Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.2.1e Puntero constante/ a constante

§1 Sinopsis

Un puntero puede ser declarado con el modificador const; recuérdese que cualquier entidad declarada con esta propiedad no puede modificar su valor ( 3.2.1c). En el caso de la declaración de punteros, la posición del modificador es determinante, de forma que pueden darse dos situaciones cuyos resultados son bastante distintos.

§2 Puntero-a-constante

Las dos formas que siguen son análogas; en ambos casos representan puntero-a-tipoX-constante (el objeto al que se apunta es constante), abreviadamente: puntero-a-constante. Se utilizan para avisar al compilador que el objeto referenciado no puede ser modificado, ni aún a través de su puntero.

tipoX const * puntero ... ;
const tipoX * puntero ... ;

Ejemplo:

int const * xPtr = &x;
const int * yPtr = &y;

xPtr e yPtr son dos objetos del mismo tipo (puntero-a-int constante). Ambos señalan a sendos enteros cuyo Rvalue no pueden ser modificado, sin embargo, los punteros si pueden ser modificados. Por ejemplo, señalando a un nuevo objeto (siempre naturalmente que este sea un int constante).


Los punteros-a-constante son de utilización más frecuente que los punteros constantes (ver a continuación). Se utilizan principalmente cuando se pasan punteros a funciones, porque se desea aligerar la secuencia de llamada ( 4.4.6b). En especial si los punteros señalan a objetos muy grandes y se desea evitar que la función invocada pueda modificar el objeto en cuestión.

Ejemplo:

class CMG {...};       // clase muy grande
void fun(const CMG*);  // función aceptando un puntero a la clase
 
int main() {           // ===========
  CMG* aptr = new CMG;
  ...
  fun(aptr);           // fun no puede modificar el objeto
}

§3 Puntero constante

La expresión que sigue significa puntero-constante-a-tipoX (el puntero es constante), abreviadamente: puntero constante. Se utiliza para informar al compilador que el valor del puntero no puede cambiar, aunque el objeto al que apunta si pueda cambiar (si no es constante).

tipoX * const puntero ... ;

Puesto que en este tipo de sentencias el puntero es declarado constante y su valor no puede ser modificado con posterioridad, hay que establecer su valor en el mismo momento de la declaración. Es decir, debe ser una definición del tipo:

tipoX * const puntero = dirección-de-objeto ;

Ejemplo:

int* const xPtr = &x;

En este caso xPtr es un puntero constante-a-int. El puntero es constante, lo que significa que su Rvalue no puede ser cambiado; por tanto debe señalar al entero x durante toda su vida.


§4 No confundir puntero constante con puntero-a-constante. Como puede verse en la última línea del ejemplo que sigue, ambos casos pueden darse simultáneamente (puntero constante a constante):

int nN = 7;                  // entero iniciado a 7
const int nC = 7;            // entero constante iniciado a 7
int *const pt1 = &nN;        // puntero constante a int
const int * pt2 = &nC;       // puntero a constante tipo int
const int * const pt3 = &nC  // puntero constante a constante tipo int

Como resumen podemos afirmar que:

  • Un puntero constante no puede ser modificado
  • Un puntero-a-constante apunta a un objeto cuyo Rvalue no puede cambiar


Observe que, desde la óptica del C++, puntero-a-tipoX y puntero-a-tipoX-constante son tipos distintos, del mismo modo que tipoX y tipoX-constante son también tipos distintos. Ejemplos:

const int kte = 75;
int x = 75;
int* ptr;           // puntero-a-int
ptr = &kte;         // Error. La asignación no es posible
const int* ptk;     // puntero-a-int-constante
ptk = &kte;         // Ok: Asignación correcta


§5 Es ilegal crear un puntero que pudiera violar la no asignabilidad de un objeto constante. Considere los ejemplos siguientes:

int i;                  // i es int (no iniciado)
const int ci = 7;       // ci es int constante (iniciado a 7)
i = ci;                 // Ok: Asigna constante-int a int (i == 7)
ci = 0;                 // ERROR: asignar valor a constante
ci--;                   // ERROR: modificar valor de constante
int * pi;               // pi es puntero-a-int (no iniciado)
int * const cp = &i;    // cp es puntero-constante-a-int (iniciado), apunta a un int no constante (i)
*cp = ci;               // Ok: Asigna constante-int al objeto apuntado por un puntero-constante-a-int (el objeto apuntado i, no es constante)
cp = &ci;               // ERROR: asignar nuevo valor a puntero constante
const int * pci = &ci;  // pci es puntero-a-constante-int
*pci = 3;               // ERROR: asignar valor al objeto apuntado por pci (es puntero-a-constante)
++pci;                  // Ok: Incrementa puntero-a-constante-int
const int * const cpc = &ci; // cpc es puntero-constante-a-constante-int
pci = cpc;              // Ok: Asigna puntero-constante-a-constante-int a un puntero-a-constante-int
cpc++;                  // ERROR: modificar valor de puntero-constante
pi = pci;               // ERROR: si se permitiese la asignación, sería posible asignar al valor apuntado por pci (una constante) mediante asignación a la dirección apuntada por pi.


Reglas similares pueden aplicarse al modificador volatile. Observe que ambos: const ( 3.2.1c ) y volatile ( 4.1.9) pueden aparecer como modificadores del mismo identificador.


§5.1  A pesar a pesar de lo indicado anteriormente, es posible modificar una constante, de forma indirecta, a través de un puntero. Considere el siguiente ejemplo [2]:

int const edad = 40;    // L1: constante tipo int iniciada a 40
printf("%3i\n", edad);  // L2: -> 40
*(int *)&edad = 35;     // L3: Se acepta la instrucción sin error
printf("%3i\n", edad);  // L4: -> 40
printf("%3i\n", *(int *)&edad);  // L5: -> 35

Comentario:

El ejemplo, un tanto sorprendente, merece una explicación:

L1:  La variable edad se define como constante-tipo-int. Se inicia al valor 40

L2:  Se muestra su valor: 40 como cabría esperar.

L3: Esta sentencia se ejecuta sin error. Modifica el valor de la constante de forma indirecta, a través de un puntero, asignándole un valor de 35.

La explicación es que la dirección &edad, Lvalue(edad), que es la dirección de una constante-tipo-int, es modificada por el operador de modelado (int *) que la promueve a puntero-a-int (aquí se pierde el carácter de constante); a este puntero lo denominaremos provisionalmente iptr. Finalmente, la expresión *iptr = 35 asigna 35 a la "variable" señalada por iptr, lo que es perfectamente legal.

L4:  Esta sorprendente sentencia indica que edad sigue teniendo el valor 40 (su valor inicial).

L5:  Acto seguido, pedimos que nos muestre el valor guardado en la dirección iptr (la dirección de edad), y nos muestra el valor modificado, 35.

La explicación de esta aparente contradicción es que (por razones de eficacia en la ejecución del código), en la línea 4 el compilador utiliza el valor de la constante resuelta en tiempo de compilación (se le había dicho -Línea 1- que era constante). El compilador supone que será realmente constante y en el segmento de código correspondiente a la sentencia de impresión pone directamente el valor 40 obtenido en tiempo de compilación. Este es el valor que nos muestra en la línea 4. Si embargo, cuando en la línea 5 se le pide que muestre el verdadero valor (el que hay en la zona de almacenamiento de variables [1]) muestra el valor modificado.

  Inicio.


[1] Recuérdese lo indicado al respecto ( 3.2.3); no está garantizado que el compilador asigne un Lvalue a los objetos declarados inicialmente como constantes.

[2] Naturalmente que todos estos "trucos" están totalmente desaconsejados, y no está garantizado que funcionen en todos los compiladores. El C++ ofrece alternativas adecuadas y portables para hacer las cosas.