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.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.*paptr1))[2] = 'r';   // M.39
*(*(c1.*paptr1)+4) = 'z';   // M.40

*(c1.*(c1.*paptr2))  ='M';   // M.42
(c1.*(c1.*paptr2))[2]='L';   // M.43
*(c1.*(c1.*paptr2)+4)='N';   // M.44

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.

  Inicio.


[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.