Esta sección muestra la forma en la que se puede usar el
especificador const
con las clases. Puede ser
interesante crear una constante local a una clase para usarla en
expresiones constantes que serán evaluadas en tiempo de
compilación. Sin embargo, el significado del especificador
const
es diferente para las clases
[64], de modo que debe comprender las
opciones adecuadas para crear miembros constantes en una clase.
También se puede hacer que un objeto completo sea constante (y como se ha visto, el compilador siempre hace constantes los objetos temporarios). Pero preservar la consistencia de un objeto constante es más complicado. El compilador puede asegurar la consistencia de las variables de los tipos del lenguaje pero no puede vigilar la complejidad de una clase. Para garantizar dicha consistencia se emplean las funciones miembro constantes; que son las únicas que un objeto constante puede invocar.
Uno de los lugares donde interesa usar const
es para
expresiones constantes dentro de las clases. El ejemplo típico
es cuando se define un vector en una clase y se quiere usar
const
en lugar de #define
para establecer el
tamaño del vector y para usarlo al calcular datos
concernientes al vector. El tamaño del vector es algo que
desea mantener oculto en la clase, así que si usa un nombre
como size
, por ejemplo, se podría usar el
mismo nombre en otra clase sin que ocurra un conflicto. El
preprocesador trata todos los #define
de forma global
a partir del punto donde se definen, algo que const
permite corregir de forma adecuada consiguiendo el efecto
deseado.
Se podría pensar que la elección lógica es colocar una
constante dentro de la clase. Esto no produce el resultado
esperado. Dentro de una clase const
recupera un poco su significado en C. Asigna espacio de
almacenamiento para cada variable y representa un valor que
es inicializado y ya no se puede cambiar. El uso de una
constante dentro de una clase significa «Esto es constante
durante la vida del objeto». Por otra parte, en cada objeto
la constante puede contener un valor diferente.
Por eso, cuando crea una constante ordinaria (no estática) dentro de una clase, no puede darle un valor inicial. Esta inicialización debe ocurrir en el constructor. Como la constante se debe inicializar en el punto en que se crea, en el cuerpo del constructor la constante debe estar ya inicializada. De otro modo, le quedaría la opción de esperar hasta algún punto posterior en el constructor, lo que significaría que la constante no tendría valor por un momento. Y nada impediría cambiar el valor de la constante en varios sitios del constructor.
Un punto especial de inicialización es la llamada «lista de inicialización del constructor» y fue pensada en un principio para su uso en herencia (tratada en el [FIXME:XREF:capítulo 14]). La lista de inicialización del constructor (que como su nombre indica, sólo aparece en la definición del constructor) es una lista de llamadas a constructores que aparece después de la lista de argumentos del constructor y antes de abrir la llave del cuerpo del constructor. Se hace así para recordarle que las inicialización de la lista sucede antes de ejecutarse el constructor. Ese es el lugar donde poner las inicializaciones de todas las constantes de la clase. El modo apropiado para colocar las constantes en una clase se muestra a continuación:
//: C08:ConstInitialization.cpp // Initializing const in classes #include <iostream> using namespace std; class Fred { const int size; public: Fred(int sz); void print(); }; Fred::Fred(int sz) : size(sz) {} void Fred::print() { cout << size << endl; } int main() { Fred a(1), b(2), c(3); a.print(), b.print(), c.print(); } ///:~
Listado 8.9. C08/ConstInitialization.cpp
El aspecto de la lista de inicialización del constructor mostrada arriba puede crear confusión al principio porque no es usual tratar los tipos del lenguaje como si tuvieran constructores.
Durante el desarrollo del lenguaje se puso más esfuerzo en hacer que los tipos definidos por el programador se pareciesen a los tipos del lenguaje, pero a veces, cuando se vio útil se hizo que los tipos predefinidos (built-in se pareciesen a los definidos por el programador. En la lista de inicialización del constructor, puede tratar a los tipos del lenguaje como si tuvieran un constructor, como aquí:
//: C08:BuiltInTypeConstructors.cpp #include <iostream> using namespace std; class B { int i; public: B(int ii); void print(); }; B::B(int ii) : i(ii) {} void B::print() { cout << i << endl; } int main() { B a(1), b(2); float pi(3.14159); a.print(); b.print(); cout << pi << endl; } ///:~
Listado 8.10. C08/BuiltInTypeConstructors.cpp
Esto es especialmente crítico cuando se inicializan
atributos constantes porque se deben inicializar antes de
entrar en el cuerpo de la función. Tiene sentido extender
este «constructor» para los tipos del lenguaje
(que simplemente significan asignación) al caso general que
es por lo que la definición float
funciona en
el código anterior. A menudo es útil encapsular un tipo del
lenguaje en una clase para garantizar la inicialización con
el constructor. Por ejemplo, aquí hay una clase
Integer
:
//: C08:EncapsulatingTypes.cpp #include <iostream> using namespace std; class Integer { int i; public: Integer(int ii = 0); void print(); }; Integer::Integer(int ii) : i(ii) {} void Integer::print() { cout << i << ' '; } int main() { Integer i[100]; for(int j = 0; j < 100; j++) i[j].print(); } ///:~
Listado 8.11. C08/EncapsulatingTypes.cpp
El vector de enteros declarado en
main()
se inicializa automáticamente a
cero. Esta inicialización no es necesariamente más costosa
que un bucle for
o
memset()
. Muchos compiladores lo
optimizan fácilmente como un proceso muy rápido.