8.4.2. Constantes en tiempo de compilación dentro de clases

El uso anterior de const es interesante y probablemente útil en muchos casos, pero no resuelve el problema original de «cómo hacer una constante en tiempo de compilación dentro de una clase». La respuesta requiere el uso de una palabra reservada adicional que no se explicará completamente hasta el Capítulo 10: static. El especificador static, en esta situación significa «hay sólo una instancia a pesar de que se creen varios objetos de la clase» que es precisamente lo que se necesita: un atributo de clase que es constante, y que no cambia de un objeto a otro de la misma clase. Por eso, una static const de un tipo predefinido se puede tratar como una constante en tiempo de compilación.

Hay una característica de static const cuando se usa dentro de clases que es un tanto inusual: se debe indicar el valor inicial en el punto en que se define. Esto sólo ocurre con static const y no funciona en otras situaciones porque todos los otros atributos deben inicializarse en el constructor o en otros métodos.

A continuación aparece un ejemplo que muestra la creación y uso de una static const llamada size en una clase que representa una pila de punteros a cadenas[66].

//: C08:StringStack.cpp
// Using static const to create a 
// compile-time constant inside a class
#include <string>
#include <iostream>
using namespace std;

class StringStack {
  static const int size = 100;
  const string* stack[size];
  int index;
public:
  StringStack();
  void push(const string* s);
  const string* pop();
};

StringStack::StringStack() : index(0) {
  memset(stack, 0, size * sizeof(string*));
}

void StringStack::push(const string* s) {
  if(index < size)
    stack[index++] = s;
}

const string* StringStack::pop() {
  if(index > 0) {
    const string* rv = stack[--index];
    stack[index] = 0;
    return rv;
  }
  return 0;
}

string iceCream[] = {
  "pralines & cream",
  "fudge ripple",
  "jamocha almond fudge",
  "wild mountain blackberry",
  "raspberry sorbet",
  "lemon swirl",
  "rocky road",
  "deep chocolate fudge"
};

const int iCsz = 
  sizeof iceCream / sizeof *iceCream;

int main() {
  StringStack ss;
  for(int i = 0; i < iCsz; i++)
    ss.push(&iceCream[i]);
  const string* cp;
  while((cp = ss.pop()) != 0)
    cout << *cp << endl;
} ///:~

Listado 8.12. C08/StringStack.cpp


Como size se usa para determinar el tamaño del array stack, es de hecho una constante en tiempo de compilación, pero que queda oculta dentro de la clase.

Fíjese en que push() toma un const string* como argumento, pop() retorna un const string* y StringStack contiene const string*. Si no fuera así, no podría usar una StringStack para contener los punteros de iceCream. Sin embargo, esto también impide hacer algo que cambie los objetos contenidos en StringStack. Por supuesto, no todos los contenedores están diseñados con esta restricción.

El enumerado en código antiguo

En versiones antiguas de C++, static const no se permitía dentro de las clases. Esto hacía que const no pudiese usarse para expresiones constantes dentro de clases. Pero muchos programadores lo conseguían con una solución típica (normalmente conocida como «enum hack») que consiste en usar un enum sin etiqueta y sin instancias. Una enumeración debe tener establecidos sus valores en tiempo de compilación, es local a una clase y sus valores están disponibles para expresiones constantes. Por eso, es habitual ver código como:

//: C08:EnumHack.cpp
#include <iostream>
using namespace std;

class Bunch {
  enum { size = 1000 };
  int i[size];
};

int main() {
  cout << "sizeof(Bunch) = " << sizeof(Bunch) 
       << ", sizeof(i[1000]) = " 
       << sizeof(int[1000]) << endl;
} ///:~

Listado 8.13. C08/EnumHack.cpp


Este uso de enum garantiza que no se ocupa almacenamiento en el objeto, y que todos los símbolos definidos en la enumeración se evalúan en tiempo de compilación. Además se puede establecer explícitamente el valor de los símbolos:

enum { one = 1, two = 2, three };

Con tipos enum enteros, el compilador continuará contando a partir del último valor, así que el símbolo three tendrá el valor 3.

En el ejemplo StringStack anterior, la línea:

static const int size = 100;

podría sustituirse por:

enum { size = 100 };

Aunque es fácil ver esta técnica en código antiguo, el uso de static const fue añadido al lenguaje precisamente para resolver este problema. Sin embargo, no existe ninguna razón abrumadora por la que deba usar static const en lugar de enum, y en este libro se utiliza enum porque hay más compiladores que le dan soporte en el momento en que se escribió este libro.



[66] En el momento de escribir este libro, no todos los compiladores permitían esta característica.