3.2.1c const
§1 Sinopsis
La palabra clave const se utiliza para hacer que un objeto-dato, señalado por un identificador, no pueda ser modificado a lo largo del programa (sea constante). El especificador const se puede aplicar a cualquier objeto de cualquier tipo, dando lugar a un nuevo tipo con idénticas propiedades que el original pero que no puede ser cambiado después de su inicialización (se trata pues de un verdadero especificador de tipo).
Cuando se utiliza en la definición de parámetros de funciones o con miembros de clases , tiene significados adicionales especiales.
§2 Sintaxis
Existen cuatro formas posibles:
const [<tipo-de-variable>] <nombre-de-variable> [ = <valor> ]; §2.1
<nombre-de-función> ( const <tipo>*<nombre-de-variable> ); §2.2
<nombre-de-metodo> const; §2.3
const <instanciado de clase>; §2.4
Nota: observe que no consideramos aquí la forma:
const [<tipo-de-variable>] <nombre-de-función> (...);
Significaría una función devolviendo un tipo constante.
Como el lector puede suponer, la pluralidad de formas sintácticas permitidas trasluce la variedad de significados que, dependiendo de las circunstancias, puede asumir esta palabra clave. Esta variedad se traduce en un comportamiento camaleónico que cuesta trabajo asimilar en toda su extensión. Máxime si se le suma el hecho de que su comportamiento puede ser afectado por ciertos especificadores adicionales.
§3 Descripción
La palabra clave const declara que un valor no es modificable por el programa. Puesto que no puede hacerse ninguna asignación posterior a un identificador especificado como const, esta debe hacerse inevitablemente en el momento de la declaración, a menos que se trate de una declaración extern ( 4.1.8d).
const float pi = 3.14; // una constante float
char *const pt1 = "Sol"; // pt1 puntero constante-a-char
char const *pt2 = "Luna"; // pt2 puntero-a-cadena-de-char constante
const char *pt2 = "Luna"; // idem.
const peso = 45; // una constante int
const float talla; // Error!! no inicializada
extern const int x // Ok. declarada extern
§4 Es importante insistir en el concepto de que tipoX-const y tipoX son tipos distintos
(no se trata solamente de que a dicha variable no se le pueda cambiar el valor). Esto tiene importantes repercusiones prácticas;
como veremos (
4.2.1a), un puntero-a-constante-tipoX no es intercambiable por un puntero-a-tipoX.
§5 Cuando no se indica <tipo-de-variable>,
en ausencia de otro especificador, const por sí misma equivale a int.
const *ptr ; // Puntero a int constante
cons x = 10; // Entero constante
§6 Un carácter entre comillas simples es un carácter constante const char
(declaración implícita), y puede ser asignado a una variable, o utilizado en cualquier lugar que un char normal:
const char letra_a = 'a';
§7 Cualquier futuro intento de asignación a una constante origina un error de compilación (aunque el resultado
exacto depende de la implementación), por lo que según las declaraciones anteriores
, las que siguen son ilegales.
pi = 3.0; // NO! Asigna un valor a una constante.
i = peso++; // NO! Incrementa una constante.
pt1 = "Marte" // NO! Apunta pt1 a otra entidad
§8 const y volatile
Aunque en principio pueda parecer un contrasentido, el especificador const puede coexistir con el atributo volatile ( 3.2.1d). Por tanto es válida la expresión:
const volatile int x = 33;
Para el compilador viene a significar que la variable x no puede aceptar modificaciones a lo largo del programa, pero que su valor inicial 33, puede variar por causas externas, por lo que no se pueden hacer suposiciones sobre su valor actual.
§9 const con punteros
Puesto que el especificador const crea un nuevo tipo, un puntero a-tipoX es considerado distinto de un puntero a-constante-tipoX. Considere el siguiente ejemplo:
int x = 10;
int* xptr = &x; // Ok.
cons int y = 10;
int* yptr = &y; // L.4: Error:
const int* yptr = &y; // Ok.
Observe que const-tipoX* no puede ser asignado a tipoX*, que es el error que
se produce en L.4. En este caso, el compilador lo expresa: "Cannot
convert 'const int *' to 'int *' ...
"; sin embargo, la inversa sí es posible, es decir: tipoX* puede
ser asignado a const-tipoX*:
int y = 10;
const int* yptr = &y; // Ok. !!
Tenga en cuenta que un
puntero declarado como constante no puede ser modificado, pero el objeto al
que señala sí que puede modificarse (de hecho, el objeto señalado no tiene
porqué ser constante). Siguiendo con las definiciones anteriores, puede hacerse:
char *const pt1 = "Sol"; // pt1 puntero constante-a-char
char *pt3 = pt1; // el puntero pt3 señala a la cadena "Sol"
pt3++; // el puntero pt3 señala a la cadena "ol"
*pt3 = 'a'; // modifica segundo carácter de "Sol"
cout << pt1; // -> "Sal"
Nota: en determinados casos, una constante puede ser indirectamente modificada a través de un puntero, aunque no sea aconsejable y el resultado dependa de la implementación. Ver una discusión mas detallada sobre este asunto de punteros y constantes: Puntero constante/a constante ( 4.2.1e).
§10 El atributo const puede ser reversible. C++ dispone de un operador
específico que puede poner o quitar este atributo de un objeto (
4.9.9a operador const_cast);
se trata de una especie de "casting" específico de la propiedad const,
aunque con ciertas limitaciones.
§11 const en parámetros de funciones
El especificador const puede ser utilizado en la definición de parámetros de funciones. Esto resulta de especial utilidad en tres casos; en los tres el fin que se persigue es el mismo: indicar que la función no podrá cambiar dichos argumentos (segundo caso de la sintaxis ):
- Con parámetros de funciones que sean de tipo matriz (que se pasan por referencia). Ejemplo:
int strlen(const char[]);
- Cuando los parámetros son punteros (a fin de que desde dentro de la función no puedan ser modificados los objetos referenciados). Ejemplo:
int printf (const char *format, ...);
- Cuando el argumento de la función sea una referencia, previniendo así que la función pueda modificar el valor referenciado. Ejemplo:
int dimen(const X &x2);
En los tres casos señalados, la declaración de los parámetros como const, garantiza que las respectivas funciones no puedan modificar el contenido de los argumentos.
§12 const con miembros de clases
El declarador const puede utilizarse con clases de tres formas distintas:
Utilizado en el sentido tradicional (objeto-dato que no puede ser modificado) §12.1
Aplicado a métodos de clase (con un sentido muy especial) §12.2
Aplicado a objetos (instancias de clase) asignándoles ciertas características §12.3
§12.1 En el sentido tradicional del especificador
Cuando se aplica a propiedades de clase, indicando que se trata de constantes cuyo valor no puede ser modificado a lo largo de la vida del programa. Ejemplo:
class C {
const int k;
// declara k constante (private)
int x;
// declara x no constante (private)
public:
int f1(C c1, const C& c2) { // declara argumento c2 constante
// (segundo caso de sintaxis)
c1.x = 3;
// Ok: objeto c1 modificable
c1.k = 4; // Error: Miembro
k no modificable
c2.x = 5; // Error: objeto
c2 NO modificable
return (x + k + c1.x + c2.k);
}
...
};
Nota: puesto que en el cuerpo de la clase no pueden hacerse asignaciones, C++ proporciona un método especial para inicializar estas constantes (caso de k) ( 4.11.2d3).
§12.2 Aplicado a métodos de clase
<nombre-de-función> const;
Si se utiliza el especificador const en la declaración de un método de clase, la función no puede modificar ninguna propiedad en la clase. Este uso del especificador no puede utilizarse con funcinoes normales, solo con funciones-miembro de clase.
Ejemplo
class C {
int x;
// Private por defecto
int func(int i) const { // tercer caso de sintaxis
x = x + i; // Error: !
}
...
};
Al compilar este código se produce un error: Cannot modify a const object in function C::func(int) const
,
avisándonos que la función func declarada constante no puede modificar el valor de la variable x
(ni ningún otro miembro de C, sea o no constante).
Nótese que si la definición de la función está fuera de la definición de la clase, también es necesario el especificador. En el ejemplo anterior:
class C {
int x;
int func(int) const;
...
};
...
inline int C::func(int i) const { /* ... */ }
Tenga en cuenta que en estos casos, el especificador const forma parte del tipo de la función miembro, de forma que:
class C {
int x;
int func(int);
...
};
...
inline int C::func(int i) const { /* ... */ }
Produce un error de compilación: 'C::func(int) const' is not a member of 'C'
Observe que el especificador const puede aparecer simultáneamente en tres posiciones distintas de la declaración de un método:
struct C {
int x;
const int& foo (const int&) const;
};
const int& C::foo(const int& i) const {
cout << "xxx " << x * i << endl;
return this->x;
}
...
void func() {
C c1 = {3};
int x = 3;
foo(x); // -> xxx 30
}
El primero significa que el valor devuelto por la función no podrá ser utilizado para modificar el objeto original. El segundo indica que el argumento recibido por la función no podrá ser modificado en el cuerpo de esta. El tercero señala que el método C::foo no podrá ser utilizado para modificar ningún miembro de la estructura C a la que pertenece.
§12.3 Aplicado a instancias de clases
Además del sentido estricto de constante, cuando se utiliza con miembros de clases, const es una característica añadida de seguridad [2]. En efecto: los objetos definidos como const intentan usar las funciones-miembro definidas a su vez como const .
En general estos objetos solo puede usar miembros que también estén definidos como const, de forma que si un objeto-const invoca un método no-const, el compilador muestra un mensaje de alerta, avisando que un objeto-const está utilizando un miembro no-const.
Ejemplo
#include <iostream.h>
class C {
int num; // privado por defecto
public:
C(int i = 0) { num = i; } // constructor por defecto
int func(int i) const { // func declarada const
// tercer caso de la sintaxis
cout << "Funcion no-modificativa." << endl;
return i++;
}
int func(int i) { //
versión no-const de func
cout << "Modifica datos privados" << endl;
return num = i; // modifica miembro num
}
int f(int i) { // función no-const
cout << "Funcion no-modificativa llamada con " << i << endl;
return i;
}
};
int main() { //
======================================
C
c_nc; // Instancia-1 Utilizará miembros no-const
const C c_c; // Instancia-2 Utilizará miembros const
// cuarto caso de la sintaxis
c_nc.func(1); // invoca versión no-const de func
c_nc.f(1); // Ok. f es función no-const
c_c.func(1); //
invoca versión cons de func
c_c.f(1); // Aviso: objeto-const invoca función no-const
}
Salida:
Modifica datos privados
Funcion no-modificativa llamada con 1
Funcion no-modificativa.
Funcion no-modificativa llamada con 1
Observe que la clase C tiene dos versiones de la misma función func. No se trata de un caso de
polimorfismo, puesto que ambas tienen exactamente la misma definición. En ausencia del especificador const en una de
ellas, el compilador hubiese dado un error: Multiple
declaration for 'C::func(int)'
, pero la presencia de este
modificador hace que el compilador considere que ambos métodos son
distintos. La distinción de cual de las dos se utilizará en cada
invocación de un objeto depende de que el propio objeto sea declarado const o no-const.
En el ejemplo se han creado dos instancias de la clase: Una normal, no-const c_nc, y otra const, c_c. Vemos que la invocación c_nc.func(1); utiliza la versión no-const, mientras que la invocación c_c.func(1); utiliza la versión constante.
Puede comprobarse también que el compilador genera un mensaje de aviso en la penúltima línea: Non-const
function C::f(int) called for const object in function main()
,
avisando que una función no-constante (f) ha sido invocada por un objeto constante (c_c).
Nota: hay forma de saltarse esta restricción: ver mutable ( 4.1.8e).
Temas relacionados:
- Constantes ( 3.2.3): Todo lo referente a los diversos tipos de constantes y su declaración.
- Cosntantes literales ( 3.2.3f): Un tipo especial de matrices de caracteres.
- Enumeradores ( 3.2.3g): Un tipo especial de variable cuyo rango es una serie de constantes enteras.
[2] Una función parecida es realizada también por el modificador volatile ( 4.1.9 ).