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 programa original de «cómo hacer una constante en tiempo de compilación dentro de una clase». La respuesta requiere del uso de un especificador adicional que se explicará completamente en el [FIXME: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 básico se puede tratar como una constante en tiempo de compilación.

Hay un 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 lo 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[65].

//: 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 vector stack, es adecuado usar 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. En cualquier caso, 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 codigo antiguo

En versiones antiguas de C++ el tipo 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 };

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

En el ejemplo StringStack anterior, la línea:

static const int size = 100;

podriá sustituirse por:

enum { size = 100 };

Aunque es fácil ver esta técnica en código correcto, el uso de static const fue añadido al lenguaje precisamente para resolver este problema. En todo caso, 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 el momento en que se escribió este libro.



[65] Al termino de este libro, no todos los compiladores permiten esta característica.