Aula Macedonia


Curso de Programación Orientada a Objetos en C++


Artículo realizado por
Fernando Rodríguez.


Capítulo 8.
Variables y funciones de miembro estáticas.

En este capítulo abordaremos el estudio de las variables y funciones de miembro estáticas. Veremos cómo podemos aprovechar su potencia para ir olvidándonos, poco a poco, de todo lo concerniente al uso de variables globales.

Este tipo de variables se caracterizan, pues, por ofrecernos una serie de servicios comunes a los que las variables globales nos han brindado siempre en el C tradicional pero aportando una mayor seguridad. Con las funciones de miembro estáticas veremos cómo acceder a ese tipo de variables de forma exclusiva.

Variables de miembro estáticas.

De todos es conocido los numerosos problemas que traen las variables globales en el momento en que acometemos un proyecto de dimensiones moderadamente serias. Enseguida empezamos a tener dudas si la variable que estamos tratando es global o no, empezamos a cambiar los valores inadvertidamente desde las funciones y, en consecuencia, empiezan a producirse fallos totalmente inesperados que terminan por hacer que reescribamos un buen montón de código que realmente no hacía falta revisar.

En la programación en C tradicional, seguro que alguna vez habéis tenido que utilizar variables globales para poder saber en cualquier momento o en cualquier función, la situación del programa. Pues bien, en el C++ las variables de miembro estáticas suponen una alternativa seria y eficaz al uso de las variables globales. Cuando declaramos una variable de miembro estática en una clase, todos los objetos que sean instancia de esa clase, usarán la misma variable de miembro estática.

Por tanto, una variable de miembro estática declarada en una clase, utilizará siempre una misma posición de memoria independientemente del número de instancias que creemos a esa clase, es decir, si la clase que contiene la variable de miembro estática tiene cien instancias, los cambios que efectuemos en la variable de miembro estática desde la instancia número 45, hará que el resto de instancias también contengan ese valor.

Uno de los usos más comunes de las variables de miembro estáticas son los derivados de contabilizar el número de instancias que tiene una clase. Si declaramos una variable de miembro estática en una clase con el nombre m_numInstancias y desde el constructor hacemos m_numinstancias++; y desde el destructor hacemos m_numInstnacias--; nos será posible conocer el número de instancias existentes desde cualquier objeto creado. Este es un buen ejemplo de comprender todo lo que se ha dicho anteriormente y lo mostraré al final de la sección correspondiente a variables estáticas.

Cómo crear una variable de miembro estática

Para declarar una variable de miembro estática lo único que debemos de hacer es anteponer la palabra clave static a la definición de tipo, es decir, si queremos declarar una variable de miembro estática de tipo entero con el nombre m_staticValor, haríamos lo siguiente:


static int m_staticValor;

Con esta simple declaración hemos creado una variable de miembro estática. A partir de este momento, m_staticValor sólo ocupará una posición de memoria. Independientemente del número de instancias que se creen de la clase que la define. Sólo existirá una posición de memoria de tal forma que realizar un cambio en el valor de m_staticValor se verá reflejado en todas las instancias de la clase que defina a m_staticValor. En este sentido, podemos observar que una variable de tipo estático tiene las mismas prestaciones que una variable de tipo global.

A la hora de acceder a una variable de miembro estática, primero debemos de tener en cuenta cómo se ha declarado la variable, es decir, si es pública, protegida o privada. Una variable de miembro estática pues, se puede acceder perfectamente desde dentro de las funciones de la clase que la definen. También se puede acceder utilizando el operador "." o "->" en caso de que la instancia que se haya creado sea un puntero. Asíe, si la clase con el nombre CEjemplo contiene una definición de la variable estática m_staticValor, podríamos hacer (siempre y cuando, la variable m_staticValor fuera pública, claro), lo siguiente:


CEjemplo prueba; // Instancia a CEjemplo.

// Accedemos a m_staticValor porque es pública
prueba.m_staticValor = 20;

Un caso a parte lo constituyen las funciones que no pertenecen a la clase que define la variable de miembro estática. En estos caso, hemos de acceder al valor estático utilizando el operador de resolución de ámbito "::". Es decir, si quisieramos acceder a m_staticValor desde la función main del programa, deberíamos de hacer CEjemplo::m_staticValor = 20 De nuevo, para que esto funcione, la variable estática m_staticValor ha de ser declarada como pública.

En líneas generales, y sin despreciar estos ejemplos anteriormente mostrados, lo ideal es crearnos variables estáticas de tipo privado y, mediante el uso de funciones estáticas de tipo público, hacer las manipulaciones pertinentes a estas poderosas variables. Las funciones estáticas, que veremos más adelante, se caracterizan, entre otras cosas, por trabajar únicamente con variables estáticas.

Un ejemplo con declaración y definición de variables estáticas

Vamos a poner ahora un ejemplo de todo las formas de acceso que hemos comentado más arriba. Vamos a ver cómo acceder a una variable de miembro estática pública mediante el operador de resolución de ámbito, mediante el operador ".", y, cómo no, mediante la utilización de una función. Crearemos dos objetos de tal forma que se compruebe cómo el cambio de valor en la variable en uno de los objetos se ve reflejado también en el otro. El ejemplo es muy sencillo.

#include <iostream.h>

// Ejemplo de cómo utilizar variables de miembro 
// estáticas.

class CEjemplo{
// Variables públicas
public:
// Declaración de la variable estática
static int m_staticValor; 
// Funciones públicas
public: 
CEjemplo(){} // Constructor
~CEjemplo() {} // Destructor
void PonValor(int valor) { m_staticValor = valor; }
int MuestraValor() { return m_staticValor; }
};

// Definición de la variable estática
int CEjemplo::m_staticValor;

void main(void)
{
	CEjemplo objeto1;  // Instancia 1
	CEjemplo* objeto2 = new CEjemplo; // Instancia 2 

	objeto1.m_staticValor = 10;
	cout << "\n el valor es: " << objeto1.m_staticValor;

	objeto2->PonValor(20);
	cout << "\n el valor es: " << objeto1.MuestraValor();

	CEjemplo::m_staticValor = 30;
	cout << "\n el valor es: " << objeto1.MuestraValor() <<
		" y es igual a: " <<	objeto2->m_staticValor << "\n\n\n";
	cin;
}

Lo primero que hacemos es declarar la variable de miembro estática tal y como habíamos visto al principio de este capítulo, dentro de la clase, esto es:


// Variables públicas
public:
// Declaración de la variable estática
static int m_staticValor;

Después definimos los constructores, destructores y funciones de miembro públicas para trabajar con esa variable:

// Funciones públicas
public: 
CEjemplo(){} // Constructor
~CEjemplo() {} // Destructor
void PonValor(int valor) { m_staticValor = valor; }
int MuestraValor() { return m_staticValor; }

A continuación, viene una parte muy importante, pues hemos de definir la variable de miembro estática que hemos declarado dentro de la clase. Esto es así por el tema de que una variable de miembro estática es compartida por múltiples funciones. Por tanto, recordad que una variable de miembro estática debe de declararse dentro de la definición de una clase y después, debe de definirse fuera de la definición de la clase. En caso de no hacer esto, el compilador os mostrará errores. Aquí tenéis la definición fuera de la clase:

// Definición de la variable estática
int CEjemplo::m_staticValor;

Ahora todo es mucho más fácil, nos limitamos a "jugar" con los posibles accesos que podemos hacer a la variable y, después, mostramos el valor por pantalla. Este es el resultado:

Ultimos apuntes sobre las variables estáticas

Antes de dejar el estudio de las variables estáticas, recordar que si estamos trabajando con variables estáticas privadas no podemos acceder "al libre albedrio" tal y como hemos hecho antes. Al declarar una variable estática privada, sólo podremos acceder mediante funciones, nunca directamente, es decir, aplicamos todos nuestros conocimientos ya adquiridos de acceso público, protegido y privado.

Recodar también que es absolutamente necesario seguir la regla de declarar dentro de la definición de la clase y definir fuera de la definición de la clase, la variable de miembro estática. Valga este último ejemplo, para mostrar una forma de contabilizar el número de instancias a una misma clase.


#include <iostream.h>

// Este ejemplo muestra cómo poder llevar
// la cuenta del número de instancias a una
// clase

class CEjemplo2{
// Variables
private:
	// Declaramos la variable estática
	static unsigned int m_contInstancias;

// Funciones
public:
	CEjemplo2() { m_contInstancias++; }
	~CEjemplo2() { m_contInstancias--; }
	unsigned int NumInstancias() { return m_contInstancias; }
};

// Definimos la variable estática
// y aprovechamos para inicializarla
unsigned int CEjemplo2::m_contInstancias = 0;

void main (void)
{
	CEjemplo2 objeto1, objeto2;
	CEjemplo2* objeto3 = new CEjemplo2;

	cout << "\nExisten " <<	objeto1.NumInstancias() <<
		" instancias a la clase CEjemplo2";
	
	cout << "\n";

	delete objeto3;
	
	cout << "\nBorramos una instancia y ahora existen " <<
		objeto2.NumInstancias() << 
		" instancias a la clase CEjemplo2 \n\n\n";
	cin;
}

La salida de este ejemplo es:

Funciones de miembro estáticas

Las funciones de miembro estáticas se caracterizan porque sólo trabajan con funciones de miembro estáticas. Al contrario que una función de miembro "convencional" (esto es, una función de miembro como las que hemos visto hasta ahora), una función de tipo estática sólo podrá trabajar, como ya he dicho, con las variables anteriormente explicadas.

Para declarar una función de miembro estática es necesario anteponer la palabra clave static antes de definir qué es lo que devuelve la función de miembro. Así, si quisiéramos declarar como función de miembro estática la función de miembro NumInstancias del último ejemplo (cosa, por otra parte, totalmente recomendable), la definición de la clase debería de tener el aspecto siguiente:

class CEjemplo2{
// Variables
private:
	// Declaramos la variable estática
	static unsigned int m_contInstancias;

// Funciones
public:
	CEjemplo2() { m_contInstancias++; }
	~CEjemplo2() { m_contInstancias--; }

	// Ahora la función NumInstancias es estática.
	static unsigned int NumInstancias() { return m_contInstancias; }
};

Fijaros en la función NumInstancias que ahora lleva la palabra clave static:

// Ahora la función NumInstancias es estática.
	static unsigned int NumInstancias() { return m_contInstancias; }

Lo que puede y no puede hacer una función estática

Una función de miembro estática no puede acceder a variables que no son estáticas, es decir, si tuviéramos definido en la clase CEjemplo2 una variable del tipo:

int m_x;

La función estática NumInstancias NO podría acceder a la variable m_x. Cuidado con esto porque si no lo sabéis podéis morir de la manera más tonta dando cabezazos a vuestro monitor ;).

Como recordaréis, el puntero this es un parámetro que se pasa "en secreto" a todas las funciones que pertenecen a una clase. Con el puntero this podemos acceder a todos y cada uno de los miembro de esa clase. Pues bien, en las funciones de miembro estáticas no se pasa el puntero this así que no podréis utilizar este valioso puntero para acceder a otros miembros desde la función del ejemplo NumInstancias.

Próximo capítulo

En el próximo capítulo, abordaremos las clases y funciones amigas "friends", que permitirá establecer lazos de unión o, mejor dicho, comunicación a distintos niveles entre distintas clases y distintas funciones.





AULA MACEDONIA
a
MACEDONIA Magazine