You are currently browsing the monthly archive for diciembre 2010.

Cuando compilamos patrones de clases (templates) en C++, como los contenedores de la STL, entonces tenemos a veces errores de compilación con mensajes exageradamente largos, que esconden más que aclaran. En este artículo quiero enseñar de como “leer” estos mensajes para fácilmente encontrar el error causante.

Primero debemos entender que el mensaje del compilador intenta mostrar toda la información posible. Según el tipo del problema, diferentes partes del mensaje pueden ser significantes. Por ejemplo, si cambio el allocator estándar de un contenedor por uno diferente, entonces me importará qué dice el mensaje de error sobre este componente. No obstante, en este artículo asumimos un uso básico de contenedores de la STL.

Más concretamente compilamos este trozo de código.

    // Una instancia de un mapa
	std::map mi_mapa;

	// Un iterator a una posición del mapa
	std::map::const_iterator const_itr = mi_mapa.begin();

	// Insertar un nuevo elemento en la posición indicada por el iterador
	mi_mapa.insert(const_itr, std::pair("atributo", "valor"));

Este código falla en la línea mi_mapa.insert.

Descifrando un mensaje de MinGW

Compilando con un compilador MinGW obtenemos el siguiente error para esta línea.

error: no matching function for call to `std::map, std::allocator > >::insert(std::_Rb_tree_const_iterator >&, std::pair)'
C:/opt/Dev-Cpp/include/c++/3.4.2/bits/stl_map.h:360: note: candidates are: std::pair<_Key, std::pair, std::_Select1st >, _Compare, _Alloc>::iterator, bool> std::map<_Key, _Tp, _Compare, _Alloc>::insert(const std::pair&) [with _Key = std::string, _Tp = std::string, _Compare = std::less, _Alloc = std::allocator >]
C:/opt/Dev-Cpp/include/c++/3.4.2/bits/stl_map.h:384: note:                 typename std::_Rb_tree<_Key, std::pair, std::_Select1st >, _Compare, _Alloc>::iterator std::map<_Key, _Tp, _Compare, _Alloc>::insert(typename std::_Rb_tree<_Key, std::pair, std::_Select1st >, _Compare, _Alloc>::iterator, const std::pair&) [with _Key = std::string, _Tp = std::string, _Compare = std::less, _Alloc = std::allocator >]

El mensaje consiste de tres líneas: primero el error de que no hay una función que corresponde a la llamada y luego dos propuestas del compilador que funciones se podría haber intentado llamar. Como nosotros no hemos escrito la función a que llamamos, sabemos que nuestra llamada debe ser mala.

El truco para descifrar este mensaje largo consiste en eliminar sucesivamente los parámetros de los patrónes (template parameters). Nos fijamos en la primera línea lo que está entre las comas altas (‘): std::map, std::allocator > >::insert(std::_Rb_tree_const_iterator >&, std::pair).

Empezamos a eliminar desde dentro de las paréntesis angulares < > todo lo que nosotros no hemos especificado en ningún sitio. Sí, correcto, simplemente lo borramos. Son parámetros por defecto que no nos interesan. Hemos definido una instancia de std::map. En el mensaje de error, estos dos parámetros de tipo string aparecen, pero luego seguido por un std::less que no hemos puesto en ningún sitio. ¡Así fuera! Lo mismo pasa con el std::allocator. Lo borramos. Haciendo esto nuestro mensaje de error queda más corto: std::map::insert(std::_Rb_tree_const_iterator >&, std::pair).

El método insert tiene dos parámetros. Correcto. El último, es realmente un std::pair, pero el primero es un _Rb_tree_const_iterator de que no sabemos de donde procede. Nuestro primer parámetro en un const_iterator. Pues bien, _Rb_tree_const_iterator contiene la palabra const_iterator y será un tipo interno para representar un iterador a constante a un mapa. ¿Pero por qué tree? Aquí nos ayuda saber que los contenedores asociativos (std::set, std::map, std::multimap) están internamente organizado como un árbol – que es un tree en inglés. Si sustituimos la palabra tree por map, entonces el primer parámetro del método insert tendra el tipo std::_Rb_map_const_iterator >. Como nuestro tipo de iterador no tiene parámetros de patrón, simplemente lo borramos y nos queda nada más que std::_Rb_map_const_iterator.

La primera línea del mensaje de error queda entonces en

std::map::insert(std::_Rb_map_const_iterator&, std::pair)

Esto se parece a la línea donde se produjo el error:

mi_mapa.insert(const_itr, std::pair("atributo", "valor"));

Este parecido entre si no es de extrañar, ya que el mensaje de error hace referencia justamente a esta llamada.

 

Ahora nos fijamos en la segunda línea que contiene una propuesta de función y la acortamos de la misma manera.

C:/opt/Dev-Cpp/include/c++/3.4.2/bits/stl_map.h:360: note: candidates are: std::pair<_Key, std::pair, std::_Select1st >, _Compare, _Alloc>::iterator, bool> std::map<_Key, _Tp, _Compare, _Alloc>::insert(const std::pair&) [with _Key = std::string, _Tp = std::string, _Compare = std::less, _Alloc = std::allocator >]

Primero eliminamos todo el texto de introducción hasta “candidates are:”. Luego eliminamos los _Compare y _Alloc que nosostros no hemos pedido y nos quedamos con esto:

std::pair<_Key, std::pair, std::_Select1st >>::iterator, bool> std::map<_Key, _Tp>::insert(const std::pair&) [with _Key = std::string, _Tp = std::string]

Recordamos que podemos reemplazar tree por map y renombamos _Rb_tree a _Rb_map. Los parámetros de patrón contiene un conjunto de tipos que no nos interesa, así simplemente lo quitamos y nos queda

std::pair std::map<_Key, _Tp>::insert(const std::pair&) [with _Key = std::string, _Tp = std::string]

Si nos ayuda podemos reemplazar _Key y _Tp por std::string como indica la última parte del mensaje. El nombre de espacio std y la palabra clave typename no nos aportan tampoco información, así los eliminamos también.

pair<_Rb_map::iterator, bool> map::insert(const pair&)

Finalmente vemos claro que la función propuesta es un método insert que tomo un tipo pair como entrada.

 

Hacemos el procedimiento a la tercera línea del mensaje de error.

C:/opt/Dev-Cpp/include/c++/3.4.2/bits/stl_map.h:384: note:                 typename std::_Rb_tree<_Key, std::pair, std::_Select1st >, _Compare, _Alloc>::iterator std::map<_Key, _Tp, _Compare, _Alloc>::insert(typename std::_Rb_tree<_Key, std::pair, std::_Select1st >, _Compare, _Alloc>::iterator, const std::pair&) [with _Key = std::string, _Tp = std::string, _Compare = std::less, _Alloc = std::allocator >]

Eliminamos los parámetros de patrones innecesarios, reemplazamos tree por map, sustituimos _Key y _Tp por std::string, y eliminamos std y typename. Y con esto llegamos a esta función propuesta:

_Rb_map::iterator std::map<string, string>::insert(_Rb_map::iterator, const pair&)

Esta propuesta ya se parece bastante a nuestra llamada

mi_mapa.insert(const_itr, std::pairstring, std::string>("atributo", "valor"));

Lo que es diferente es el primer parámetro: nosotros usamos un const_iterator mientras la función propuesta requiere un iterator sin const. Si cambiamos la línea

std::mapstring, std::string>::const_iterator const_itr = mi_mapa.begin();

por

std::mapstring, std::string>::iterator ya_no_const_itr = mi_mapa.begin();

entonces compila.

Entender a Microsoft Visual Studio

Como ejercicio puedes usar las mismas técnicas para entender el resultado de compilación del mismo código con Microsoft Visual Studio.

Error	1	error C2664: 'std::_Tree<_Traits>::iterator std::_Tree<_Traits>::insert(std::_Tree<_Traits>::iterator,const std::pair<_Ty1,_Ty2> &)' : no se puede convertir el parámetro 1 de 'std::_Tree<_Traits>::const_iterator' a 'std::_Tree<_Traits>::iterator'

error C2664: 'std::_Tree<_Traits>::iterator std::_Tree<_Traits>::insert(std::_Tree<_Traits>::iterator,const std::pair<_Ty1,_Ty2> &)' : no se puede convertir el parámetro 1 de 'std::_Tree<_Traits>::const_iterator' a 'std::_Tree<_Traits>::iterator'
1>        with
1>        [
1>            _Traits=std::_Tmap_traits,std::allocator>,false>,
1>            _Ty1=const std::string,
1>            _Ty2=std::string
1>        ]
1>        and
1>        [
1>            _Traits=std::_Tmap_traits,std::allocator>,false>
1>        ]
1>        and
1>        [
1>            _Traits=std::_Tmap_traits,std::allocator>,false>
1>        ]
1>        No hay disponible ningún operador de conversión definido por el usuario que pueda realizar esta conversión, o bien no se puede llamar al operador

Conclusión

Al inicio se pueden copiar los mensajes de error largos a un editor de texto y modificar ahí sucesivamente el mensaje hasta sólo queda la parte significativa. Con un poco de práctica, uno consigue hacer estas reducciones en la mente. La mayor parte del mensaje está “contaminado” por parámetros de patrones por defecto, que se necesitan por razones técnicas pero que no se usan como usuario de una clase.

La experiencia enseña también, que la mayoría de los errores son del mismo estilo: mezclar iterator y const_iterator o confundirse en los parámetros o valores de retorno de métodos sobrecargados.

Referencias

Guardar datos de forma segura implica dos necesidades

  1. Recuperar los datos perdidos.
  2. Prevenir que personas no autorizadas puedan acceder a ellos.

El primer punto trata de fallos técnicos o robo, el segundo por la protección de datos mediante contraseñas y encriptación. Ambos dominios son demasiados amplios para ser tratados en detalle aquí, así me limito a dar unas consideraciones a tener en cuenta.

Es casi seguro que alguna vez un dispositivo de almacenamiento deja de funcionar y se pierden todos los datos. Por lo tanto, guardar datos en sólo un sitio es una manera casi segura de perderlos algún día. Por eso se crean redundancias mediante copias de seguridad (back-up en inglés).

La forma más simple y directa es hacer una copia de seguridad en el mismo disco duro. Es una buena manera para guardar cambios pequeños, por ejemplo, para guardar cada hora el documento Word en que estás trabajando actualmente. Conviene mantener varias copias, porque a veces no te enteras que un documento fue contaminado por un virus o dañado de otra forma hasta días después. Por este motivo un sistema de respaldo en tiempo real (RAID) tampoco da toda la seguridad: no copia sólo los documentos en tiempo real, sino también los virus. Sin embargo, es, sin duda, el mejor sistema para protegerse contra fallos de hardware y una necesidad para una base de datos o cualquier otro sistema que nunca debe fallar.

Un back-up en el mismo disco duro no protege contra la pérdida de datos en caso de fallo del disco duro. Por lo tanto, conviene tener un segundo disco duro. Puede estar en la misma máquina o uno externo. Si tu ordenador no sufre una sobretensión que destruye sus componentes, estás a salvo. Más seguro aún es guardar copias de seguridad en un lugar físicamente desconectado del equipo. Basta con desenchufar el disco duro externo o guardar los datos más importante en un pen-drive o un DVD regrabable.

Esto te protege contra una pérdida total del ordenador pero no contra un robo o un incendio en tu casa u oficina. Para prevenir esto puedes guardar datos en la nube, es decir, en algún servidor en Internet. Esto es muy práctico, ya que puedes acceder a estos datos desde cualquier sitio, pero tiene la desventaja que ya no sabes quién más lee y usa estos datos. O igual ya te lo imaginas: empresas que ofrecen publicidad a medida o las autoridades que están constantemente preocupados que algún terrorista todavía no esté contratado o al menos conocido por el servicio secreto de tu país. Una manera de fastidiarles el negocio con tus datos personales es publicarlos en WikiLeaks. Otra es guardarlos en un DVD en la casa de tu abuela. La forma más segura de proteger tus datos sigue siendo no enviarlos por Internet. Y tampoco tiene por qué ser la más lenta. No subestimes el ancho de banda de un camión cargado con DVDs.

Más seriamente, guardar datos importantes fuera de hogar, pero en un sitio de confianza, protege contra males mayores. Igual como guardarías documentos importantes en papel en una caja fuerte en un banco, lo podrías hacer con un DVD con datos que no quieres perder – aunque sean sólo las fotos de tu boda. Y no olvides encriptar los datos si lo ves necesario.

El artículo “Guía definitiva para mantener tus datos seguros en caso de robo o pérdida de tu equipo” por Goyko Alexander Obrenovich Vinces da razones y consejos prácticos sobre elegir contraseñas, sistemas de respaldo e incluso una propuesta de como localizar tu equipo en caso de robo.

Referencias

Este artículo trata de como aprovechar una hoja de cálculo para inicializar tablas en la base de datos.

Dentro de una base de datos guardamos tablas que consideramos “de configuración” ya que no se suelen modificar pero, sí, leerse al menos durante la inicialización del programa. Como estas tablas se modifican pocas veces, es habitual no crear una interfaz gráfica para rellenarlas sino se editan sus datos directamente con una herramienta de administración de la base de datos.

Las tablas de configuración pueden contener datos muy parecidos entre si. Por ejemplo, una tabla de configuración podría definir una red de ordenadores de la siguiente manera:

Ejemplo de una tabla de configuración
Id Nombre Dirección IP
1 Ordenador 1 192.168.0.1
2 Ordenador 2 192.168.0.2
3 Ordenador 3 192.168.0.3
n Ordenador n 192.168.0.n

Como vemos, los datos en cada registro son casi iguales. La parte variable he marcado con una n en la última línea.

Editar una tabla así puede ser pesado cuando se tratan de muchas entradas. Por eso buscamos una manera más sofisticada. Una es crear los comandos INSERT con una hoja de cálculo como Microsoft Excel o Open Office Calc.

Hoja de cálculo para crear comandos INSERT
A B C D
1 Id Nombre Dirección IP Comando SQL
2 1 =”Ordenador ” & A1 =”192.168.0.” & A1 =”insert into mi_tabla values(” & A1 & “, ‘” & B1 & “‘, ‘” & C1 &”‘);”
3 =A1+1 Repite fórmula arriba
4 Repite fórmula arriba

Con “Repite fórmula arriba” me refiero a “copiar hacia abajo” la fórmula de la primera fila.

Con las fórmulas en la hoja de cálculo anterior obtenemos el siguiente resultado.

Hoja de cálculo resultante
A B C D
1 Id Nombre Dirección IP Comando SQL
2 1 Ordenador 1 192.168.0.1 insert into mi_tabla values(1, ‘Ordenador 1’, ‘192.168.0.1’);
3 2 Ordenador 2 192.168.0.2 insert into mi_tabla values(2, ‘Ordenador 2’, ‘192.168.0.2’);
4 3 Ordenador 3 192.168.0.3 insert into mi_tabla values(3, ‘Ordenador 3’, ‘192.168.0.3’);

La última columna podemos copiar y pegar en un fichero de texto y ejecutarlo en el administrador de la base de datos. Así añadimos todas las entradas de golpe sin tener que editar todas las entradas a mano.

insert into mi_tabla values(1, 'Ordenador 1', '192.168.0.1');
insert into mi_tabla values(2, 'Ordenador 2', '192.168.0.2');
insert into mi_tabla values(3, 'Ordenador 3', '192.168.0.3');

Conviene guardar la hoja de cálculo junto con el proyecto para poder fácilmente cambiar la configuración como añadir una nueva fila para un nuevo ordenador o una nueva columna para una característica adicional.

Referencias

Introducción

A la primera vista uno diría que no es posible programar orientado a objetos con el lenguaje de programación C porque no tiene clases. Esto es parcialmente correcto, ya que clases son un componente importante para programar orientado a objetos. El lenguaje C, en cambio, fue diseñado para la programación estructurada. Sin embargo, el lenguaje C permite definir objetos.

Programar orientado a objetos es un paradigma, es decir, una forma de diseñar código. Hay lenguajes como C++ o Java que están diseñados para este paradigma, pero no se programa orientado a objetos meramente por utilizar a uno de estos lenguajes. Por ejemplo, si todos los miembros de una clase son públicos y estáticos, entonces equivalen a variables y funciones globales con el prefijo “NombreDeClase::”. (Prefijos pueden reemplazar espacio de nombres.)

En cambio, se puede aplicar el paradigma de orientado a objetos a lenguajes de programación no diseñados para esto, aunque será lógicamente con complicaciones. Como resolver estas complicaciones para el lenguaje de programación C es el tema de este artículo.

Definición de clases

Los datos de una clase se pueden guardar en una estructura. De hecho, en C++ un struct no es otra cosa que una clase con todos los miembros públicos. Como C no permite métodos, usamos funciones globales. El equivalente de C++

class MiClase
{
public:
	int miDato;
	void hazAlgo(void)
	{
	    miDato = 5;
	}
};

en C sería

struct MiClase
{
	int miDato;
}

void hazAlgo(MiClase *const miInstancia)
{
    miInstancia->miDato = 5;
}

Nótese que el primer parámetro de la función en C tiene el mismo tipo que el puntero this en C++.

El uso de estas clases sería en C++

MiClase miInstancia;
miInstancia->hazAlgo();

y en C

MiClase miInstancia;
MiClase_hazAlgo(&miInstancia);

Mediante punteros a funciones y macros es posible que se puede construir el aspecto de llamadas a métodos de C++ como en

miInstancia->miMetodo();

Sin embargo, no lo vamos a discutir en este artículo. Se trata meramente de un aspecto estético que, en mi opinión, más confunde que aclara. El lenguaje C no es C++ y programación orientado a objetos no deja de serlo sólo por ordenar de otra forma los componentes de una llamada a una función.

Estructuras opacas

Es posible declarar y utilizar un tipo de puntero a una estructura sin definir la estructura misma. Con este mecanismo se pueden crear estructuras opacas.

En el fichero de cabecera (el .h) defino un tipo de puntero a una estructura

typedef struct MiStruct* MiClase;

La definición de la estructura estará dentro de un fichero de implementación (*.c).

struct MiStruct
{
    int miDato;
};

El interfaz en el fichero de cabecera define un puntero, que es un tipo de un tamaño conocido. Por eso el compilador puede compilar un programa que usa un puntero aunque el tamaño del elemento a que apunta es desconocido. El usuario de MiClase accede a la biblioteca meramente con el puntero.

Este encapsulamiento de datos es incluso mejor que el de C++ ya se define la estructura de datos en el fichero de definición. Este fichero no tiene por qué ser visible para el usuario de la estructura opaca, ya que se puede meramente publicar los ficheros binarios correspondientes. En otras palabras, se puede esconder por completo la estructura de datos. Esto no es posible con una clase en C++, ya que la declaración de la clase en el fichero de cabecera debe contenedor todos los campos de datos.

Herencia

No existe un mecanismo de clases derivadas en C, sin embargo esto se puede simular. El método de una clase/estructura, que hereda de otra, puede convertir el puntero de su estructura a una estructura de la clase base y llamar con ello los métodos de esta. Esto corresponde a una herencia privada. Para esto hace falta que la primera parte de la estructura derivada contiene en el inicio los mismos campos como la estructura base.

Para hacer una herencia pública, el usuario debe convertir el puntero en su llamada.

MiClaseDerivada miInstancia;
MiClaseBase_HazAlgo((MiClaseBase) miInstancia);

Obviamente esta construcción es aún menos práctica ya que es el usuario debe pensar si llama a una función de la clase base o de la clase derivada.

La herencia complica más en el lenguaje C que ayuda. Por esto vale la pena tener en cuenta una alternativa: usar una clase en lugar de heredar de ella.

Identificadores en lugar de punteros

Una manera potencialmente más potente para la programación orientado a objetos es abstraer de los punteros y referenciar objetos nada más que por un identificador numérico. Cada método de una clase debe primero comprobar si el identificador numérico corresponde a su clase correspondiente. Esto es más complejo pero una buena alternativa cuando se quieren manejar muchas clases diferentes en C. De hecho, muchos lenguajes modernos como Java, C# o PHP devuelven identificadores y no punteros a los objetos, lo cual les permite reordenador los objetos en la memoria si hace falta. Sólo el gestor de objetos sabe bajo qué dirección de memoria se encuentra qué objeto.

Un camino intermedio y buen compromiso es exportar un tipo de identificador diferente para cada clase. Entonces la clase gestiona nada más que sus propias instancias.

Resumen

Hemos visto que se pueden crear “clases” y “objetos” con el lenguaje de programación C. Referenciar objetos por punteros a estructuras opacas o mediante identificadores es una manera potente para el encapsular datos.

Por no ser un lenguaje diseñado para la programación orientado a objetos, el uso de este parádigma a veces no queda muy elegante en el código y tampoco es posible aprovechar la potencia de la herencia sin trucos adicionales.

No obstante, la programación orientado a objetos es posible con el lenguaje C y debería tenerse en cuenta a la hora de realizar proyectos grandes. De hecho hay unas bibliotecas que se han creado de esta forma como el servidor X o la biblioteca GObject.

Referencias

Introducción

Igual como la palabra clave const, el modificador static trae confusiones porque se puede utilizar en varios contextos en C y C++. Podemos declarar estáticas funciones y métodos. Variables pueden estar estáticas dentro de funciones, métodos, clases y a nivel global, es decir, por todas partes.

Variables estáticas locales

Lo más común es declarar una variable local estática:

void mi_funcion_o_metodo(void)
{
    static int var_estatica = 4;
}

El valor de la variable var_estatica no se pierde aunque ya no se encuentre dentro del alcance, es decir, cuando el programa haya salido de la función en que la variable fue definida. Esto diferencia una variable estática de una variable no estática, cuyo valor se pierde al final de la función.

Debido a esta diferencia, las variables estaticas no se guardan en el stack sino en una memoria especial para variables estáticas. Aunque no sean accesibles durante toda la ejecución del programa, las variables estáticas existen durante toda la ejecución del programa.

Igual como en el caso de las variables convencionales, se llama al constructor de las variables estáticas cuando se construyen. Esto succede una vez antes de llamar a la función main. Igualmente se llama una vez al destructor después de salir del programa. Recuerda que las variables estáticas existen durante toda la ejecución del programa. Por eso deben inicalizarse antes de entrar y después de salir de la función principal.

No hay diferencia entre variables estáticas en funciones y métodos de clases. De hecho, una variable estática se puede definir dentro de cualquier bloque delimitado por {}.

Miembros estáticos de una clase

Variables miembros de una clase también pueden ser estáticas. Una variable estática de una clase es un dato común a todas las instancias de esta clase y accesible en todos los métodos. Una variable miembro estática es incluso accesible sin instancia alguna.

En el código siguiente definimos una clase con una variable miembro estática:

class MiClase
{
public:
    static int mi_variable;
};

Esta variable se inicializa así:

int MiClase::mi_variable = 5;

Si la queremos usar sin instancia, entonces usamos el nombre cualificado:

void haz_algo(void)
{
    MiClase::miVariable = 3;
}

Lógicamente, la variable miembro debe ser pública para que la podamos usar fuera de los métodos de la clase. (Dentro de los métodos de la clase no hace falta poner el nombre de la clase delante.)

Las variables miembros estáticas existen también durante toda la ejecución del programa. Su diferencia con las variables estáticas definidas en una función o método está en su alcance: Las variables miembros están al alcance de todos los métodos de una clase y no sólo de uno.

Constantes estáticas

Variables estáticas se pueden declarar también const. Una constante estática es una constante universal del programa, pero de alcance limitado. Es una buena práctica utilizar constantes estáticas en lugar de macros #define ya que las constantes llevan un tipo: un 5 puede ser un int, un char o un float pero un dato

const static int var = 5;

siempre es un int.

Métodos estáticos

Métodos de clases también pueden ser estáticas. Declarar un método estático es una promesa que este método sólo utiliza miembros estáticos de la clase. Este tipo de método no conoce el puntero this, por lo cual no se puede hacer directamente referencia a métodos y variables no estátitocs de la clase.

class MiClase
{
public:
    // Una variable estática
    static int una_variable_estatica;

    // Una variable no estática
    int una_variable_no_estatica;

    // Un método estático
    static void haz_algo(void)
    {
        // Se pueden usar variables estáticas
        una_variable_estatica = 5;

        // Error: no se pueden usar variables que
        // requieren un puntero this
        una_variable_no_estatica = 5;  // Error de compilación
    }

    // Un método normal (no estático)
    void haz_otra_cosa(void)
    {
        // Se pueden usar variables y métodos estáticos
        // también en métodos no estáticos
        una_variable_estatica = 5;
        haz_algo();

        // Una variable de instancia en un método de instancia
        una_variable_no_estatica = 5;  // Correcto
    }
};

Es posible llamar a métodos estáticos de una clase sin tener una instancia de ella utilizando el nombre cualificado.

void una_funcion_cualquiera(void)
{
    // Correcto: Se puede llamar a un método no estático
    // si instancia
    MiClase::haz_algo();

    // Sin embargo, también se le puede llamar con instancia
    MiClase mi_instancia;
    mi_instancia.haz_algo();

    // No es posible llamar a un método convencional
    // sin instancia
    MiClase::haz_otra_cosa();  // Error de compilación
}

Métodos estáticos son similares a funciones globales. Sólo llevan el nombre de la clase como prefijo. (Un nombre de espacio no se usa de forma diferente que una clase con todos los miembros públicos y estáticos.)

Métodos estáticos pueden sobrecargar los nombres métodos no estático. De hecho es una práctica bastante común – sobre todo para métodos recursivos. Como ejemplo podemos considerar una clase String, en que un método convencional afecta a la instancia de una clase, mientras el método estático a un parámetro.

class String
{
public:

    // Crea una instancia con todas las letras del parámetro 
    // convertidas a minúsculas.
    static String a_minusculas(const String& instancia_de_string);

    // Convierte todas las letras de ESTA instancia a minúsculas
    void a_minusculas(void)
    {
        *this = a_minusculas(*this);
    }
};

Enlace interno

Finalmente es posible declarar estáticos funciones y variables globales, es decir, fuera de una función. Una variable a nivel global existe durante toda la ejecución del programa sea estática o no. Y cualquier función tiene acceso a una variable global. Por eso static tiene otro significado fuera de un bloque {}: especifica que el identificador tiene enlace interno.

Enlace interno quiere decir, que el linker no puede usar este nombre para enlazarlo con otro módulo. Es un nombre privado en el fichero de objeto (.o o .obj). El contrario es un enlace externo: El fichero objeto expone este nombre y su tipo al linker que puede usar esta información para enlazarlo con una referencia en otro módulo.

El modificador static en una función o variable suprime el enlace externo. Se puede forzar un enlace externo con el modificador extern. Por defecto, funciones tienen un enlace externo y variables interno. Por eso no es necesario declarar una función extern y una variable static, pero mejora la semántica del código.

Conclusión

Hemos visto que la palabra clave static se usa para declarar variables de un alcance limitado pero con una vida durante toda la ejecución del programa. Métodos estáticos se limitan a acceder a variables miembros estáticos de la clase. Finalmente se usa el modificador static para forzar un enlace interno.

Como última nota cabe mencionar, que el uso de variables estáticas puede complicar la creación de procesos multi-hilo, ya que estos datos son comunes a todos los hilos.

Referencias

Escribe tu dirección de correo electrónico para suscribirte a este blog, y recibir notificaciones de nuevos mensajes por correo.

Únete a otros 56 seguidores

Archivos

diciembre 2010
L M X J V S D
« Nov   Ene »
 12345
6789101112
13141516171819
20212223242526
2728293031