4.2.1g1-3 Punteros externos a miembros normales ( no-estáticos)
§4 Punteros externos
Recordemos que los punteros-a-miembros no estáticos que hemos denominado "externos", son aquellos que no son miembros de la clase cuyos miembros se referencian. Para ilustrar su mecánica de uso utilizaremos sendos ejemplos que son una prolongación de los anteriores (utilizados para ilustrar los punteros internos).
§4.1 Ejemplo-3
La estructura del espacio global de este programa es calcada del ejemplo-1
( 4.2.1g1-2). La única
novedad es que en las líneas 37 a 45 hemos incluido 9 punteros externos que señalan a los miembros de la clase (que ya nos son
conocidos). Salvo los tres primeros (L.37/38/39) que señalan a un int, a una matriz de char y a un método de la
clase, los demás apuntan a miembros que son a su vez punteros. Es importante reseñar que estos nueve elementos no son matrices
ni miembros de ninguna clase, son simples punteros del espacio global.
A pesar de su aspecto complicado el programa es análogo a los anteriores. Se trata de un mero conjunto de ejemplos sintácticos relativos al uso de punteros-a-miembro. La función main solo incluye instrucciones de salida y unas pocas asignaciones, de forma que los objetos ya definidos adopten nuevos valores.
#include <iostream> // Ejemplo-3
using namespace std;
void fun () { cout << "Funcion externa-1" << endl; }
void fan () { cout << "Funcion externa-2" << endl; }
int x = 103, y = 301;
char achar[5];
class C {
public:
int x, y; // miembro int
char achar[5]; // miembro array de char
void fun (); // miembro funcion (metodo)
void fan (); // miembro funcion (metodo)
int* pint1; // L.16 miembro puntero-a-int
int C::* pint2; // L.17 miembro puntero-a-miembro-int
char (*pachar1)[5]; // L.18 miembro puntero-a-matriz de char
char (C::* pachar2)[5]; // L.19 miembro puntero-a-miembro-matriz de char
void (* pfe)(); // L.20 miembro puntero-a-funcion externa
void (C::* pfm)(); // L.21 miembro puntero-a-funcion miembro
C (int n = 0) { // constructor por defecto
x = n; y = 2*n;
pint1 = &::x; // L.24
pint2 = &C::x;
achar[0]='A'; achar[1]='E'; achar[2]='I'; achar[3]='O'; achar[4]='U';
pachar1 = &::achar;
pachar2 = &C::achar;
pfe = &::fun; // L.28
pfm = &C::fun; // L.29
}
};
void C::fun() { cout << "Funcion interna-1. x ==" << x << endl; }
void C::fan() { cout << "Funcion interna-2. x ==" << x << endl; }
// definici¢n de punteros externos-a-miembro (p-a-m)
int C::* iptr = &C::x;
// L.37 p-a-m int
char (C::* acptr)[5] = &C::achar; // L.38 p-a-m matriz de char
void (C::* fptr)() = &C::fun; // L.39 p-a-m metodo
int* C::* pptr1 = &C::pint1;
// L.40 p-a-m puntero-a-int
int C::* C::* pptr2 = &C::pint2;
// L.41 p-a-m puntero-a-miembro int
char (*C::* paptr1)[5] = &C::pachar1; // L.42 p-a-m puntero-a-matriz de char
char (C::*C::* paptr2)[5] = &C::pachar2; // L.43 p-a-m puntero-a-miembro matriz de char
void (*C::* pfptr1)() = &C::pfe; // L.44 p-a-m puntero-a-funcion externa
void (C::*C::* pfptr2)() = &C::pfm; // L.45 p-a-m puntero-a-funcion miembro
int main (void) { // ========================
C c1(10), c2(13); // M.1: Instancias de C
achar[0]='a'; achar[1]='e'; achar[2]='i'; achar[3]='o'; achar[4]='u';
//cout << " 0.1- c1.iptr == " << c1.iptr; ERROR
cout << " 1.2- c1.x == " << c1.*iptr
<< endl;
cout << " 2.1- c1.achar == " << c1.*acptr << endl;
cout << " 3.1- Invocar c1.fun == "; (c1.*fptr)();
cout << " 3.2- Invocar c2.fun == "; (c2.*fptr)();
//cout << " 4.1- c1.pptr1 == " << c1.pptr1; ERROR
cout << " 5.1- c1.pint1 == " << c1.*pptr1 << endl;
cout << " 5.2- c2.pint1 == " << c2.*pptr1 << endl;
cout << " 6.1- ::x == " << *(c2.*pptr1) << endl;
//cout << " 7.1- c1.pint2 == " << c1.*pptr2; ERROR
//cout << " 8.1- c1.pint2 == " << c1.(c1.*pptr2); ERROR
cout << " 9.1- c1.x == " << c1.*(c1.*pptr2) << endl;
cout << "10.1- ::achar == " << *(c1.*paptr1) << endl;
cout << "11.1- ::achar[0] == " << **(c1.*paptr1) << endl;
cout << "12.1- c1.achar == " << c1.*(c1.*paptr2) << endl;
cout << "13.1- c1.achar[0] == " << *(c1.*(c1.*paptr2)) << endl;
cout << "14.1- Invocar ::fun == "; (c1.*pfptr1)();
cout << "15.1- Invocar c1.fun == "; (c1.*(c1.*pfptr2))();
cout << "Algunas modificaciones runtime ---------------\n";
iptr = &C::y; // M.22
cout << "16.1- c1.y == " << c1.*iptr
<< endl;
cout << "16.2- c2.y == " << c2.*iptr
<< endl;
(c2.*iptr) = 123; // M.25
cout << "16.3- c2.y == " << c2.y
<< endl;
*(c1.*acptr) = 'F'; // M.27
(c1.*acptr)[2] = 'R'; // M.28
*((c1.*acptr)+4) = 'Z'; // M.29
cout << "17.1- c1.achar == " << c1.*acptr << endl;
fptr = &C::fan; // M.31
cout << "18.1- Invocar c1.fan == "; (c1.*fptr)();
cout << "18.2- Invocar c2.fan == "; (c2.*fptr)();
*(c1.*pptr1) = 130; // M.34
c1.*(c1.*pptr2) = 310; // M.35
cout << "19.1- ::x == " << *(c2.*pptr1) << endl;
cout << "20.1- c1.x == " << c1.*(c1.*pptr2) << endl;
**(c1.*paptr1) = 'f'; // M.38
(*(c1.*paptr1))[2] = 'r'; // M.39
*(*(c1.*paptr1)+4) = 'z'; // M.40
cout << "21.1- ::achar == " << *(c1.*paptr1) << endl;
*(c1.*(c1.*paptr2)) ='M'; // M.42
(c1.*(c1.*paptr2))[2]='L'; // M.43
*(c1.*(c1.*paptr2)+4)='N'; // M.44
cout << "22.1- c1.achar == " << c1.*(c1.*paptr2) << endl;
return 0;
}
Salida:
1.2- c1.x == 10
2.1- c1.achar == AEIOU
3.1- Invocar c1.fun == Funcion interna-1. x ==10
3.2- Invocar c2.fun == Funcion interna-1. x ==13
5.1- c1.pint1 == 0041B178
5.2- c2.pint1 == 0041B178
6.1- ::x == 103
9.1- c1.x == 10
10.1- ::achar == aeiou
11.1- ::achar[0] == a
12.1- c1.achar == AEIOU
13.1- c1.achar[0] == A
14.1- Invocar ::fun == Funcion externa-1
15.1- Invocar c1.fun == Funcion interna-1. x ==10
Algunas modificaciones runtime ---------------
16.1- c1.y == 20
16.2- c2.y == 26
16.3- c2.y == 123
17.1- c1.achar == FEROZ
18.1- Invocar c1.fan == Funcion interna-2. x ==10
18.2- Invocar c2.fan == Funcion interna-2. x ==13
19.1- ::x == 130
20.1- c1.x == 310
21.1- ::achar == feroz
22.1- c1.achar == MELON
Comentario
El primer punto a destacar son las definiciones de los nuevos punteros (líneas L37/45); en especial las declaraciones que
corresponden a la parte izquierda (Lvalues) de las expresiones de asignación. Por su parte los Rvalues no presentan ninguna
singularidad. Como hemos señalado anteriormente (
4.2.1g) nos limitamos a aplicar el operador de referencia &
(
4.9.11) al nombre cualificado
(
4.1.11c) del miembro.
Preste atención a las declaraciones de L44 y L45 cuyas sintaxis quizás sean lógicas, pero desde luego no "evidentes". En especial, la declaración del puntero-a-miembro pfptr2 y su posterior utilización para invocar a la función (L.45 y salida 15):
void (C::*C::* pfptr2)() = &C::pfm; // declaración del puntero
(c1.*(c1.*pfptr2))(); // invocación del método de instancia
El cuerpo de main es muy parecido estructuralmente al ejemplo-1, de forma que pueden compararse las salidas de
igual número para verificar la simetría que existe entre ambos. Las diferencias consisten en que aquí los objetos se
referencian a través de punteros externos.
Observe que las expresiones utilizadas son muy similares. En el ejemplo anterior accedíamos a los miembros de clase por el
método estándar, utilizando el identificador del objeto seguido del selector directo
. (
4.9.16) y del nombre del miembro (objeto.miembro). Aquí el acceso
se consigue sustituyendo el nombre del miembro por la indirección de su puntero (objeto.*puntero).
Nota: recuerde que el operador de deferencia de punteros-a-miembros de clase .*, tiene una precedencia menor que la del selector directo ..
Las instrucciones correspondientes a las salidas 0, 4, 7 y 8 se han mantenido como demostración de que es erróneo
cualquier intento de obtener el valor de un puntero a miembro (ver comentario en Ejemplo-1
4.2.1g1-2).
Las salidas 1 y 9 obtienen el mismo resultado, el valor actual (10) de la propiedad x del objeto c1. En el
primer caso, el camino seguido para alcanzar la propiedad es: objeto.puntero-a-miembro, de forma que el objeto se
consigue con una indirección (
4.9.11) del puntero ( c1.*iptr ). En la segunda salida
el camino es un poco más complicado: objeto.puntero-a-puntero-a-miembro,
resultando que son necesarias dos indirecciones para conseguir x: ( c1.*(c1.*pptr2) ).
El grupo de salidas 3 muestra la invocación de los métodos de dos objetos a través de su puntero. Lo verdaderamente notable es que se han obtenido valores diferentes para la versión de un (aparentemente único) puntero sobre dos instancias de la clase. Puede comprobarse como, tanto por su comportamiento como por la sintaxis empleada, los nueve punteros externos-a-miembro podrían considerarse "casi" como auténticos miembros de la clase.
Las sentencias de salidas 6 y 9 evidencian la distinta sintaxis para acceder a un elemento mediante un puntero externo según el miembro referenciado sea puntero-a objeto-externo o puntero-a objeto-miembro (los paréntesis de estas expresiones no son prescindibles).
*(c1.*pptr1) // puntero-a-miembro que es puntero-a-int-externo
c1.*(c1.*pptr2) // puntero-a-miembro que es puntero-a-int-miembro
Las salidas 10 y 11 se corresponden con 12 y 13. La diferencia estriba en que las primeras acceden a un array externo, y las segundas a un array-miembro. Observe que las expresiones que proporcionan la salida completa de ambos arrays (10 y 12):
*(c1.*paptr1) // --> aeiou
c1.*(c1.*paptr2) // --> AEIOU
en realidad contienen la dirección de inicio de ambas matrices (valor de los punteros-miembro). El compilador se encarga de conocer que estos valores son el punto de inicio de sendos arrays de tamaño 5 y el objeto cout se encarga de mostrarlos en todo su contenido. Si aplicamos una indirección adicional a estos valores, obtenemos el objeto señalado por el puntero. En este caso, el primer elemento de ambas matrices.
**(c1.*paptr1) // --> a
*(c1.*(c1.*paptr2)) // --> A
Las salidas 14 y 15 representan respectivamente la invocación de una función ::fun y un método c.fun a través de puntero-a-puntero-a-función. En estas expresiones tampoco son prescindibles los paréntesis.
La parte de main que sigue a las modificaciones runtime contiene una serie de expresiones de asignación muy interesantes. Muestran la sintaxis apropiada para modificar los objetos señalados por los punteros, así como algunas salidas para comprobación de los nuevos valores.
La sentencia M.22 realiza una nueva asignación al puntero iptr que modifica la que se realizó en su definición (L.37). A su vez la sentencia M.25 modifica el objeto (propiedad de instancia) señalado por el puntero. El grupo de salidas 16 verifica el resultado de estas modificaciones.
El grupo de asignaciones que sigue, M.27/28/29, es de lo más interesante: representan la modificación de elementos de una matriz de caracteres a través de un puntero-a-miembro. A continuación la salida 17 sirve de comprobación de los cambios efectuados.
*(c1.*acptr) = 'F'; // M.27
(c1.*acptr)[2] = 'R'; // M.28
*((c1.*acptr)+4) = 'Z'; // M.29
Recordemos que acptr es puntero-a-miembro-matriz achar de cinco elementos char (L.38) y que a su vez,
achar puede ser tomado como puntero a su primer elemento (
4.3.2).
M.27 representa la modificación del objeto señalado por el puntero. Puesto que c1.*acptr representa a achar, su indirección representa al primer elemento "A" de la matriz, valor que es sustituido por "F".
La sentencia M.29 representa la indirección del "puntero" achar después de sumarle cuatro unidades, lo que significa modificar el valor actual del último carácter "U" de la matriz, por "Z".
La sentencia M28 representa una variación del anterior, con la diferencia de que en vez de utilizar la aritmética de
punteros, usamos la de subíndices. Observe que estamos aplicando la notación de subíndices (de matrices) a un puntero. Observe
también como esta expresión tiene un operador de indirección * menos que
cualquiera de las otras dos (que son equivalentes). Esto se debe a que "aplicar subíndices a un puntero- a- matriz equivale
a aplicarle el operador de indirección" (
4.3.2).
La sentencia M.31 es muy parecida a M.22. También aquí se asigna un nuevo valor a un puntero-a-miembro externo fptr, que modifica el que se asignó en su definición (L.39). Las salidas 18, que muestran el resultado para dos objetos de la clase, vuelven a poner de manifiesto como los punteros-a-miembro externos pueden ser tomados casi como propiedades de clase.
Los grupos M38/39/40 y M42/43/44 realizan función análoga a la del grupo M27/28/29 ya comentado
. La diferencia es que ahora la cadena de acceso a la
matriz de caracteres es un poco más larga; en vez de ser referenciada mediante puntero-a-matriz, aquí el acceso se realiza
mediante puntero-a-puntero-a-matriz. La consecuencia es que donde antes se necesitaban una/dos indirecciones
*, ahora se necesitan dos o tres para acceder al objeto.
**(c1.*paptr1) = 'f'; // M.38 |
*(c1.*(c1.*paptr2)) ='M'; // M.42 |
La diferencia entre ambos grupos estriba en que en el primero, el objeto accedido (matriz) es externo, mientras que en el segundo la matriz es miembro. Observe que la precedencia de los operadores involucrados hace imprescindible la presencia de paréntesis auxiliares en las sentencias 39 y 40.
§4.2 Ejemplo-4
El caso que se presenta guarda con el anterior
() la misma relación que el
ejemplo 2 con el 1. Se mantiene la estructura con pequeñas modificaciones y también aquí se han trasladado las salidas a una
función auxiliar fs, para mostrar la sintaxis de acceso mediante un puntero a la clase.
#include <iostream> // Ejemplo-4
using namespace std;
void fun () { cout << "Funcion externa-1" << endl; }
void fan () { cout << "Funcion externa-2" << endl; }
int x = 103, y = 301;
char achar[5];
class C {
public:
int x, y; // miembro int
char achar[5]; // miembro array de char
void fun (); // miembro funcion (metodo)
void fan (); // miembro funcion (metodo)
int* pint1; // L.16 miembro puntero-a-int
int C::* pint2; // L.17 miembro puntero-a-miembro-int
char (*pachar1)[5]; // L.18 miembro puntero-a-matriz de char
char (C::* pachar2)[5]; // L.19 miembro puntero-a-miembro-matriz de char
void (*pfe)(); // L.20 miembro puntero-a-funcion externa
void (C::*pfm)(); // L.21 miembro puntero-a-funcion miembro
C (int n = 0) { // constructor por defecto
x = n; y = 2*n;
pint1 = &::x; // L.24
pint2 = &C::x;
achar[0]='A'; achar[1]='E'; achar[2]='I'; achar[3]='O'; achar[4]='U';
pachar1 = &::achar;
pachar2 = &C::achar;
pfe = &::fun; // L.28
pfm = &C::fun; // L.29
}
};
void C::fun() { cout << "Funcion interna-1. x ==" << x << endl; }
void C::fan() { cout << "Funcion interna-2. x ==" << x << endl; }
// definici¢n de punteros externos-a-miembro (p-a-m)
int C::* iptr = &C::x;
// L.37 p-a-m int
char (C::* acptr)[5] = &C::achar; // L.38 p-a-m matriz de char
void (C::* fptr)() = &C::fun; // L.39 p-a-m metodo
int* C::* pptr1 = &C::pint1; // L.40 p-a-m puntero-a-int
int C::* C::* pptr2 = &C::pint2; // L.41 p-a-m puntero-a-miembro int
char (*C::* paptr1)[5] = &C::pachar1; // L.42 p-a-m puntero-a-matriz de char
char (C::*C::* paptr2)[5] = &C::pachar2; // L.43 p-a-m puntero-a-miembro matriz de char
void (*C::* pfptr1)() = &C::pfe; // L.44 p-a-m puntero-a-funcion
void (C::*C::* pfptr2)() = &C::pfm; // L.45 p-a-m puntero-a-funcioon miembro
void fs(C*); // funcion de salidas
int main (void) { // ========================
achar[0]='a'; achar[1]='e'; achar[2]='i'; achar[3]='o'; achar[4]='u';
C* pc1 = new C(10);
fs(pc1);
return 0;
}
void fs(C* cpt) {
cout << " 1.2- c1.x == " << cpt->*iptr << endl;
cout << " 2.1- c1.achar == " << cpt->*acptr << endl;
cout << " 3.1- Invocar c1.fun == "; (cpt->*fptr)();
cout << " 5.1- c1.pint1 == " << cpt->*pptr1 << endl;
cout << " 6.1- ::x
== " << *(cpt->*pptr1) << endl;
cout << " 9.1- c1.x == " << cpt->*(cpt->*pptr2) << endl;
cout << "10.1- ::achar == " << *(cpt->*paptr1) << endl;
cout << "11.1- ::achar[0] == " << **(cpt->*paptr1) << endl;
cout << "12.1- c1.achar == " << cpt->*(cpt->*paptr2) << endl;
cout << "13.1- c1.achar[0] == " << *(cpt->*(cpt->*paptr2)) << endl;
cout << "14.1- Invocar ::fun == "; (cpt->*pfptr1)();
cout << "15.1- Invocar c1.fun == "; (cpt->*(cpt->*pfptr2))();
cout << "Algunas modificaciones runtime ---------------\n";
iptr = &C::y; // M.22
cout << "16.1- c1.y == " << cpt->*iptr
<< endl;
(cpt->*iptr) = 123; // M.25
cout << "16.3- c1.y == " << cpt->*iptr
<< endl;
*(cpt->*acptr) = 'F'; // M.27
(cpt->*acptr)[2] = 'R'; // M.28
*((cpt->*acptr)+4) = 'Z'; // M.29
cout << "17.1- c1.achar == " << cpt->*acptr << endl;
fptr = &C::fan; // M.31
cout << "18.1- Invocar c1.fan == "; (cpt->*fptr)();
*(cpt->*pptr1) = 130; // M.34
cpt->*(cpt->*pptr2) = 310; // M.35
cout << "19.1- ::x == " << *(cpt->*pptr1) << endl;
cout << "20.1- c1.x == " << cpt->*(cpt->*pptr2) << endl;
**(cpt->*paptr1) = 'f'; // M.38
(*(cpt->*paptr1))[2] = 'r'; // M.39
*(*(cpt->*paptr1)+4) = 'z'; // M.40
cout << "21.1- ::achar == " << *(cpt->*paptr1) << endl;
*(cpt->*(cpt->*paptr2)) = 'M'; // M.42
(cpt->*(cpt->*paptr2))[2]= 'L'; // M.43
*(cpt->*(cpt->*paptr2)+4)= 'N'; // M.44
cout << "22.1- c1.achar == " << cpt->*(cpt->*paptr2) << endl;
}
Salida:
1.2- c1.x == 10
2.1- c1.achar == AEIOUÐ
3.1- Invocar c1.fun == Funcion interna-1. x ==10
5.1- c1.pint1 == 0041B178
6.1- ::x == 103
9.1- c1.x == 10
10.1- ::achar == aeiou
11.1- ::achar[0] == a
12.1- c1.achar == AEIOU
13.1- c1.achar[0] == A
14.1- Invocar ::fun == Funcion externa-1
15.1- Invocar c1.fun == Funcion interna-1. x ==10
Algunas modificaciones runtime ---------------
16.1- c1.y == 20
16.3- c1.y == 123
17.1- c1.achar == FEROZ
18.1- Invocar c1.fan == Funcion interna-2. x ==10
19.1- ::x == 130
20.1- c1.x == 310
21.1- ::achar == feroz
22.1- c1.achar == MELON
Comentario:
A la luz de los aprendido en los ejemplos anteriores es fácil seguir la mecánica y notación utilizadas. Aunque el
procedimiento de obtención de resultados es ligeramente distinto, estos son iguales que en el ejemplo precedente (ejemplo-3
).
Puesto que la instancia c de la clase C no es directamente accesible, el acceso se consigue aplicando el operador de indirección sobre su puntero cpt. A continuación una nueva indirección del puntero-a-miembro permite acceder a estos.
Aparte de las asignaciones L.44 y L.45, ya señaladas en el comentario del ejemplo anterior, es también digna de mención la sintaxis de invocación del método de instancia a través de su puntero pfptr2 (salida 15): (cpt->*(cpt->*pfptr2))(); (teniendo expresiones así ¿Quién necesita enemigos? :-))
§5 Notas particulares
§a Los ejemplos anteriores compilan sin problemas con BC++ 5.5 y GNU Cpp 2.95.3. Sin embargo, con las opciones
por defecto, MS VC++ 6.0 produce un error en L.28 del Ejemplo: error C2440: '=' : cannot convert from 'char (*)[5]' to
'char (C::*)[5]
. Posiblemente se deba a un error de dicho compilador o que se necesite una opción de compilación
desconocida para mi.
§b Si en la inicialización existente en L.25 para pint1 (miembro puntero-a-int) del Ejemplo 1
( 4.2.1g1-2), se
sustituye la dirección de la variable ::x del espacio global:
pint1 = &::x; // L.25
por la dirección del miembro x:
pint1 = &x; // L.25-bis OK!!
ninguno de los compiladores probados (BC++ y GNU Cpp) produce error, aunque supuestamente deberían indicarlo, ya que el tipo de x es distinto en ambos casos. Probablemente se trata de un error de ambos compiladores; de una conversión de tipo realizada automáticamente por el compilador, o de una excepción intencionada (podría argumentarse que a fin de cuentas ambos objetos son int). Sin embargo, la sustitución inversa no es posible. Si en la inicialización de L.26 para pint2 (miembro puntero-a-miembro-int) se sustituye el miembro C::x:
pint2 = &C::x; // L.26
por la variable global ::x
pint2 = &::x; // L.26-bis ERROR!!
ambos compiladores "protestan" y generan un error indicando que la conversión de tipo no es imposible (el puntero no es del tipo adecuado).
Desde luego, se trate de un error, o de un "casting" automático, realizado en un caso sí y en otro no, por el compilador, quizás sea este tipo de peculiaridades y alguna otra que veremos al tratar de los punteros-a-miembros estáticos, las que han inducido al propio Stroustrup a calificar los punteros a miembros como un "punto oscuro" de C++.
§6 Advertencia
Los miembros de clase deben ser declarados dentro del cuerpo de la clase, pero hemos repetido en varias ocasiones que, salvo
contadas excepciones, dentro de esta no están permitidas las asignaciones
( 4.11.2a).
La situación puede ser esquematizada como sigue:
class C {
int x;
// Ok. declaración de miembro
int C::* xptr = &C::x; // ERROR!! Asignación no permitida
}
La inicialización [1] de miembros debe realizarse en los constructores
( 4.11.2d1), cuya existencia tiene
precisamente esta finalidad. Como se ha visto en los ejemplos anteriores, el procedimiento correcto de inicialización es:
class C {
int
x; // declaración de miembros
int C::* xPtr;
C()
{ // Constructor
x =
0; // Ok. asignación correcta
xPtr = &C::x;
}
}
Existe una excepción cuando los miembros son funciones (métodos). En este caso la declaración y definición pueden realizarse dentro del cuerpo de la clase, aunque la definición también puede estar fuera:
class C {
int x;
public:
void putX(int a) { x = a; } // Ok. declaración+definición
int getX() {}
// Ok. declaración (prototipo)
}
int C::getX(){ return x; } // Ok. definición externa
En ocasiones podría parecer que esto también es posible con los punteros-a-miembro. Considere el siguiente ejemplo:
#include <iostream>
using namespace std;
class C {
public:
int x;
int* xPtr; //
L.7: declaración
C(int a) {
x = a;
}
}
int C::* xPtr = &C::x; // L.12: asignación externa?
int main (void) { // ========================
C c1 = (10);
cout << " c1.x == " << c1.x << endl;
cout << " c1.x == " << c1.*xPtr << endl; // M.3:
return 0;
}
Salida:
c1.x == 10
c1.x == 10
El programa compila sin dificultad y las salidas proporcionadas son correctas. Sin embargo, en determinadas circunstancias, la utilización de xPtr produciría un error. Por ejemplo, añadiendo la siguiente sentencia a main, no se obtiene ningún error de compilación, pero se produce un error fatal en runtime:
cout << " c1.x == " << *(c1.xPtr) << endl; // M.4:
La explicación a este "extraño" comportamiento, es que en realidad, la sentencia L.12 no es la inicialización del miembro xPtr declarado en L.7, sino la declaración de un nuevo puntero externo-a-miembro en el espacio global: ::xPtr. De forma que el miembro C::xPtr queda sin inicializar; su valor es basura y señala a una posición de memoria arbitraria.
En M.3 se está utilizando el puntero externo ::xPtr, pero M.4 es la indirección del miembro xPtr del objeto c1. Como no ha sido inicializado correctamente y su valor es basura, el resultado puede ser igualmente basura o un error de runtime si la dirección señala a un punto fuera del espacio asignado a la aplicación.
[1] No confundir la "inicialización" de un miembro (o una variable cualquiera), que es la asignación inicial de valor al patrón de bits que se ha reservado para su almacenamiento, con la "asignación" posterior de nuevos valores a este espacio, operación esta que puede realizarse desde cualquier punto en que el miembro resulte accesible.