10.3. Miembros estáticos en C++

A veces se necesita un único espacio de almacenamiento para utilizado por todos los objetos de una clase. En C, podría usar una variable global pero eso no es muy seguro. Los datos globales pueden ser modificados por todo el mundo y su nombre puede chocar con otros idénticos si es un proyecto grande. Sería ideal si los datos pudiesen almacenarse como si fuesen globales pero ocultos dentro de una clase y claramente asociados con esa clase.

Esto es posible usando atributos static. Existe una única porción de espacio para los atributos static, independientemente del número de objetos de dicha clase que se hayan creado. Todos los objetos comparten el mismo espacio de almacenamiento static para ese atributo, constituyendo una forma de «comunicarse» entre ellos. Pero los datos static pertenecen a la clase; su nombre está restringido al interior de la clase y puede ser public, private o protected.

10.3.1. Definición del almacenamiento para atributos estáticos

Puesto que los datos static tienen una única porción de memoria donde almacenarse, independientemente del número de objetos creados, esa porción debe ser definida en un único sitio. El compilador no reservará espacio de almacenamiento por usted. El enlazador reportará un error si un atributo miembro es declarado pero no definido.

La definición debe realizarse fuera de la clase (no se permite el uso de la sentencia inline), y sólo está permitida una definición. Es por ello que habitualmente se incluye en el fichero de implementación de la clase. La sintaxis suele traer problemas, pero en realidad es bastante lógica. Por ejemplo, si crea un atributo estático dentro de una clase de la siguiente forma:

class A {
  static int i;
public:
  //...
};

Deberá definir el almacenamiento para ese atributo estático en el fichero de definición de la siguiente manera:

int A::i = 1;

Si quisiera definir una variable global ordinaria, debería utilizar

int i = 1;

pero aquí, el operador de resolución de ámbito y el nombre de la clase se utilizan para especificar A::i.

Algunas personas tienen problemas con la idea que A::i es private, y pese a ello parece haber algo que lo está manipulando abiertamente. ¿No rompe esto el mecanismo de protección? Ésta es una práctica completamente segura por dos razones. Primera, el único sitio donde esta inicialización es legal es en la definición. Efectivamente, si el dato static fuese un objeto con un constructor, habría llamado al constructor en lugar de utilizar el operador =. Segundo, una vez se ha realizado la definición, el usuario final no puede hacer una segunda definición puesto que el enlazador indicaría un error. Y el creador de la clase está forzado a crear la definición o el código no enlazaría en las pruebas. Esto asegura que la definición sólo sucede una vez y que es el creador de la clase quien la lleva a cabo.

La expresión completa de inicialización para un atributo estático se realiza en el ámbito de la clase. Por ejemplo,

//: C10:Statinit.cpp
// Scope of static initializer
#include <iostream>
using namespace std;

int x = 100;

class WithStatic {
  static int x;
  static int y;
public:
  void print() const {
    cout << "WithStatic::x = " << x << endl;
    cout << "WithStatic::y = " << y << endl;
  }
};

int WithStatic::x = 1;
int WithStatic::y = x + 1;
// WithStatic::x NOT ::x

int main() {
  WithStatic ws;
  ws.print();
} ///:~

Listado 10.18. C10/Statinit.cpp


Aquí el calificador WithStatic:: extiende el ámbito de WithStatic a la definición completa.

Inicialización de vectores estáticos

El capítulo 8 introdujo una variable static const que le permite definir un valor constante dentro del cuerpo de una clase. También es posible crear arrays de objetos estáticos, ya sean constantes o no constantes. La sintaxis es razonablemente consistente:

//: C10:StaticArray.cpp
// Initializing static arrays in classes
class Values {
  // static consts are initialized in-place:
  static const int scSize = 100;
  static const long scLong = 100;
  // Automatic counting works with static arrays.
  // Arrays, Non-integral and non-const statics 
  // must be initialized externally:
  static const int scInts[];
  static const long scLongs[];
  static const float scTable[];
  static const char scLetters[];
  static int size;
  static const float scFloat;
  static float table[];
  static char letters[];
};

int Values::size = 100;
const float Values::scFloat = 1.1;

const int Values::scInts[] = {
  99, 47, 33, 11, 7
};

const long Values::scLongs[] = {
  99, 47, 33, 11, 7
};

const float Values::scTable[] = {
  1.1, 2.2, 3.3, 4.4
};

const char Values::scLetters[] = {
  'a', 'b', 'c', 'd', 'e',
  'f', 'g', 'h', 'i', 'j'
};

float Values::table[4] = {
  1.1, 2.2, 3.3, 4.4
};

char Values::letters[10] = {
  'a', 'b', 'c', 'd', 'e',
  'f', 'g', 'h', 'i', 'j'
};

int main() { Values v; } ///:~

Listado 10.19. C10/StaticArray.cpp


Usando static const de tipos enteros puede realizar las definiciones dentro de la clase, pero para cualquier otro tipo (incluyendo listas de enteros, incluso si estos son static const) debe realizar una única definición externa para el atributo. Estas definiciones tienen enlazado interno, por lo que pueden incluirse en ficheros de cabecera. La sintaxis para inicializar listas estáticas es la misma que para cualquier agregado, incluyendo el conteo automáticoautomatic counting.

También puede crear objetos static const de tipos de clase y listas de dichos objetos. De todas formas, no puede inicializarlos utilizando la sintaxis tipo «inline» permitida para static const de tipos enteros básicos:

//: C10:StaticObjectArrays.cpp
// Static arrays of class objects
class X {
  int i;
public:
  X(int ii) : i(ii) {}
};

class Stat {
  // This doesn't work, although 
  // you might want it to:
//!  static const X x(100);
  // Both const and non-const static class 
  // objects must be initialized externally:
  static X x2;
  static X xTable2[];
  static const X x3;
  static const X xTable3[];
};

X Stat::x2(100);

X Stat::xTable2[] = {
  X(1), X(2), X(3), X(4)
};

const X Stat::x3(100);

const X Stat::xTable3[] = {
  X(1), X(2), X(3), X(4)
};

int main() { Stat v; } ///:~

Listado 10.20. C10/StaticObjectArrays.cpp


La inicialización de listas estáticas de objetos tanto constantes como no constantes debe realizarse de la misma manera, siguiendo la típica sintaxis de definición estática.