Sobre los punteros-a-miembro de clases
A continuación se comentan algunas cuestiones adicionales sobre la mecánica de los punteros-a-miembros no estáticos
de clases.
Sea el siguiente caso:
class C {
int i; // miembro
};
...
int C::* pmi = &C::i; // puntero-a-miembro
La existencia de una situación como la anterior, donde la definición del un puntero como pmi se realiza
de forma genérica, asignándole la dirección de una propiedad de clase (del tipo &C::i), en vez de la dirección de
un ente concreto (un miembro de instancia como &cj.i, donde cj es una instancia de la clase C),
es lo que confiere a los punteros-a-miembro unas características y una mecánica de funcionamiento distinta de las que poseen
el resto de punteros a objetos o a funciones ordinarios.
Observe que desde el mismo momento de la definición de un puntero estándar:
int x = 10;
int* iptr = &x;
el compilador puede determinar sin ambigüedad la dirección señalada por iptr, lo que significa que el valor &iptr está perfectamente definido. En cambio, tras la definición de un puntero-a-miembro como:
int C::* pmi = &C::i;
la dirección señalada por pmi es indefinida; el puntero señala al miembro de un objeto que posiblemente todavía no existe, por lo que una expresión como &pmi no tendría sentido. En realidad, la información que puede obtener el compilador de una definición como esta, es la posición relativa ("offset") del miembro i de "cualquier" instancia de C.
Es significativo que en este tipo de punteros, la definición se realiza realmente en el momento de su uso (cuando se le aplica el operador de indirección). Es entonces cuando se proporciona la información complementaria que falta: cual es el objeto sobre el que se aplicará el desplazamiento contenido en pmi para obtener finalmente el miembro correspondiente.
Nota: esta circunstancia, de señalar desplazamientos en lugar de direcciones absolutas, es la responsable de que cuando dos punteros señalan a miembros no estáticos del mismo objeto, el puntero del miembro declarado en último lugar computa como mayor que el declarado en primer lugar. Esto suponiendo que entre ambas declaraciones no medie un especificador de acceso. La razón de esta última advertencia es que es potestad de la implementación organizar los elementos por categoría de acceso. Ejemplo:
class C {
public:
int x;
char* c;
...
private:
float f;
...
};
Está garantizado que para cualquier objeto C c, el valor de un puntero-a-x sea mayor que el de puntero-a-c, pero no que puntero-a-x sea mayor que puntero-a-f.
El incremento de complejidad exigido a los punteros-a-miembro respecto de los punteros ordinarios se pone de manifiesto
incluso en su propio tamaño como se muestra en el siguiente ejemplo (resultados obtenidos con el compilador Borland C++ 5.5):
#include <iostream>
using namespace std;
class B {
public:
int i;
void foo () { cout << "B::foo()" << endl; }
};
int B::* pmi = &B::i; // puntero-a-propiedad
void (B::* pfi)() = &B::foo; // puntero-a-metodo
int main() { // ================
int* pi;
cout << "pi: " << sizeof(pi) << " Bytes" << endl;
cout << "pmi: " << sizeof(pmi) << " Bytes" << endl;
cout << "pfi: " << sizeof(pfi) << " Bytes" << endl;
return 0;
}
Salida:
pi: 4 Bytes
pmi: 8 Bytes
pfi: 12 Bytes
La necesidad de información complementaria, que debe proporcionarse en el momento de la indirección, resulta aún más
evidente en casos como el siguiente:
// Puntero miembro-a-miembro externo (de otra clase)
class A {
friend class C;
int x;
public: A(int n = 0 ) { x = n; } // constructor por defecto
};
class C {
int i; // miembro entero
int A::* aptr;
// miembro puntero-a-miembro externo
C()
{ // constructor
x = 0;
aptr =
&A::x; // el puntero señala a
miembro
de A
}
};
...
A a1(1), a2(2), a2(3); //
instanciamos algunos objetos...
B b1(10), b2(11), b3(12);
En estas circunstancias una expresión como &aptr carece sentido, o al menos es incompleta: ¿A que objeto
B pertenece el puntero deferenciado? ¿A que objeto A pertenece el miembro x que se obtiene?
Resulta evidente que una definición de aptr como la anterior ( aptr = &A::x; ), exige que en la posterior indirección del puntero se aporten dos elementos adicionales sin ninguna conexión entre si:
- El puntero-miembro que se deferencia, lo que exige especificar un objeto de C. Por ejemplo cj.aptr.
- La instancia concreta aj de A sobre la que se aplicará la indirección.
En el apartado correspondiente a la indirección de punteros
(
4.9.11) se muestra como C++ tiene dos operadores específicos para este
menester, que aceptan esta información adicional a través de sus operandos.