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.1g2 Punteros a miembros estáticos

§1 Sinopsis

Recordemos que, por las razones ya señaladas ( 4.2.1g), los punteros a miembros no-estáticos no son considerados como auténticos punteros a objeto. Por contra, dado que los miembros estáticos ( 4.11.7) se parecen más a objetos normales de un subespacio que a miembros de clase, sus punteros sí pueden ser considerados como punteros ordinarios.

Lo mismo que ocurre con los punteros a miembros-no estáticos, podríamos hacer aquí una subdivisión de los casos que pueden presentarse con punteros p a miembros estáticos m de una clase C:

1.-  Punteros que son miembros de C (punteros internos a miembros estáticos).

1.a.-  Punteros que son miembros normales (no-estáticos)

1.b.-  Punteros que son miembros estáticos

2.-  Punteros que son objetos externos a la clase (punteros externos a miembros estáticos).

§2 Declaración de Punteros a miembros estáticos

La primera observación a tener en cuenta es que puede tomarse la dirección de miembros estáticos si, y solo si, tales miembros tienen una definición fuera de la clase. Es decir, solo pueden declarárseles punteros si se cumple dicha condición. Ejemplo:

class C {
  public:
  static const int k1 = 3;     // definicion
  static const int k2 = 4;
};
const int C::k1;               // definición [1]
 
...
int main () {
  const int* ptr1 = &C::k1;   // Ok.
  const int* ptr2 = &C::k2    // Error!!
  ...
}

Comentario: respecto a este punto, el comportamiento de los compiladores difiere según su grado de adaptación al Estándar. Visual C++ 6.0 de MS no permite realizar definiciones de propiedades estáticas enteras en el interior de la clase. En cambio Borland C++ 5.5 no solo permite definir dichas propiedades en el interior de la clase, sino incluso tomar la dirección de una de estas variables aunque no exista una definición en el exterior (probablemente esto último se deba a un error de este compilador). GNU Cpp 2.95 sí parece adecuarse al Estándar.

§2.1 Ejemplo-1

Para ilustrar el uso de punteros a miembros estáticos y las características especiales de este tipo de miembros, comenzaremos por un caso sencillo que contempla la segunda hipótesis del epígrafe anterior (punteros a miembros estáticos que son externos a la clase).


#include <iostream>    // Punteros externos a miembros estáticos
using namespace std;
class C {
  public:
  static int x;
  static int* p;       // L.6
  static char* c;      // L.7
  static void fun ();  // L.8
  C () {               // L.9 constructor por defecto
    x = 13;
    p = &C::x;         // L.11
    c = "AEIOU";
  }
};
 
void C::fun () { cout << "Valor miembro x == " << x << endl; }
int C::x = 3;          // L.15 Iniciadores de miembros
int* C::p = &C::x;
char* C::c = "aeiou";

int main () {          // ========================
// Declaraciones de punteros genéricos a cada tipo:
  int* xptr;           // M.2 puntero-a-int
  int** pptr;          // M.3 puntero-a-puntero-a-int
  char** cptr;         // M.4 puntero-a-puntero-a-carácter
  void (* fptr) ();    // M.5 puntero-a-función
// Asignaciones de punteros a miembros concretos (de clase):
  xptr = &C::x;        // M.7
  pptr = &C::p;
  cptr = &C::c;
  fptr = &C::fun;
  C c1;                // M.11 Instancia de C
// Invocaciones de miembros (de instancia) a través de sus punteros
  cout << "Valor c1.x == " << *xptr << endl;
  cout << "Valor c1.x == " << **pptr << endl;
  cout << "Valor c1.c == " << *cptr << endl;
  (*fptr)();           // M.16
  return 0;
}

Salida:

Valor c1.x == 13
Valor c1.x == 13
Valor c1.c == AEIOU
Valor miembro x == 13

Comentario:

A la luz de los expuesto en el capítulo anterior, el ejemplo está plagado de sorpresas... En primer lugar, hemos visto que, aunque aceptable, la sentencia L.6 no es la forma adecuada de designar un puntero-a-miembro (que es como se utiliza en L.11). Sin embargo, al intentar utilizar la forma correcta:

int C::* p;      // L.6b miembro puntero-a-miembro-int
...
p = &C::x;       // L.11 ERROR!!

se obtiene un error de compilación al intentar posteriormente la asignación en L.11: Cannot convert 'int *' to 'int C::*'.... Curiosamente el compilador no considera que el tipo de &C::x sea puntero-a-miembro-de-C!!. Una vez más se pone de manifiesto que los miembros estáticos no son propiamente miembros de instancia. Como podemos ver en el resto de asignaciones, estos miembros son considerados meramente como objetos de un subespacio y su existencia es totalmente independiente de la existencia de instancias concretas de la clase.

Así pues, nos encontramos ante otra de esas inconsistencias que tanto se reprochan a C++. Vemos que ahora no es posible la declaración de estos como punteros-a-miembro tipoX-de-claseC, sino que deben ser declarados como simples punteros a-tipoX. Es decir, como punteros a objetos normales.

La segunda peculiaridad son las definiciones de las propiedades x, p y c fuera del cuerpo de la clase (líneas 15 a 17) que son necesarias en estos casos (§2 ). Es importante observar que si estos miembros no hubiesen sido estáticos, se habría obtenido un error de compilación: Multiple declaration for 'C::x'.... Recuerde que C++ no permite definiciones múltiples ( 4.1.2) y que la definición de métodos fuera del cuerpo de la clase (L.14) es siempre un caso especialmente autorizado.

Las líneas M.2/5 definen cuatro punteros al estilo clásico que luego son asignados sin inconveniente a miembros de la clase (líneas M.7/10). A su vez las sentencias M.13/16 acceden a los objetos señalados al estilo tradicional, esto es, utilizando el operador * de indirección ( 4.9.11) sobre el puntero.

Recordar que M.16 puede ser sustituida por:

(fptr)();        // M.16b
fptr();          // M.16c


  Es ilustrativo observar que en todo el programa no se ha utilizado para nada la instancia c1 de C, aunque su mera declaración ha influenciado en los valores de salida por obra y gracia del constructor, pues su invocación ha modificado los valores de inicio de los miembros estáticos [2]. Puede comprobarse que si eliminamos la sentencia M.11 de main en la que se crea el objeto, el programa funciona correctamente, aunque la nueva salida es [3]:

Valor c1.x == 3
Valor c1.p == 3
Valor c1.c == aeiou
Valor miembro x == 3

§2.2 Ejemplo-2

Para completar, ofrecemos una versión simétrica de la anterior pero añadiendo un puntero-a-objeto.


#include <iostream>
using namespace std;

void func (C*);       // prototipo de funcion auxiliar
class C {
  public:
  static int x;
  static int* p;
  static char* c;
  static void fun () { cout << "Valor miembro x == " << x << endl; }
  C () {              // contructor por defecto
    x = 13;
    p = &x;
    c = "AEIOU";
  }
};
int C::x  = 3;        // Iniciadores de miembros
int* C::p = &C::x;
char* C::c = "aeiou";
// definición de punteros
int* xptr = &C::x;    // puntero-a-int asignado a miembro-x
int** pptr = &C::p;   // puntero-a-puntero-a-int asignado a miembro-p
char** cptr = &C::c;  // puntero-a-puntero-a-char asignado a miembro-c

int main (void) {     // ========================
  C* cp = new C;
  func(cp);
}
void func (C* cpt) {  // función auxiliar
  cout << "S.1 c.x == " << *xptr     << endl;
  cout << "S.2 c.x == " << **pptr   << endl;
  cout << "S.3 c.c == " << *cptr     << endl;

  cout << "S.4 c.p == " << cpt->p   << endl;
  cout << "S.5 c.x == " << *(cpt->p) << endl;
//cout << "S.5 c.x == " << cpt->*cpt->p ERROR!!
  cout << "S.6 c.c == " << cpt->c   << endl;
  cout << "S.7 c.c[1]== " << *(cpt->c) << endl;
}

Salida:

S.1 c.x == 13
S.2 c.x == 13
S.3 c.c == AEIOU
S.4 c.p == 0041A220
S.5 c.x == 13
S.6 c.c == AEIOU
S.7 c.c[1]== A

Comentario:

Tal como afirmamos en el ejemplo anterior, vemos que el objeto (lo denominamos c) solo tiene una influencia indirecta en los valores de salida a través del constructor (invocado por el operador new).

Las tres primeras salidas no tienen ninguna consideración especial que no hayamos señalado antes. Solo recordar que en realidad no utilizan el objeto c (ni su puntero cpt), de forma que estas sentencias podrían estar en cualquier parte siempre que las definiciones de xptr, pptr y cptr fuesen visibles (para esto las hemos sacado de main y las hemos situado en el espacio global del fichero).

Las salidas 4 a 7 si merecen un comentario especial:

La primera sorpresa es la propia existencia de S.4. Esta sentencia, que obtiene el valor de un puntero-a-miembro, no había sido posible anteriormente (como ejemplo, ver la salida 1.2 del ejemplo-2 en la página anterior 4.2.1g1). Como es usual al tratarse de un puntero, su valor se obtiene en hexadecimal ( 2.2.4b).

S.5 obtiene el valor del miembro x mediante la indirección de su puntero p (que es a su vez miembro de la clase), sin embargo la sintaxis utilizada *(cpt->p) es la que corresponde a puntero-interno-a-int, no la de puntero-interno-a-miembro-int cpt->*cpt->p.

La salida S.6 es análoga a S.4 en el sentido que proporciona el valor del puntero (que es una dirección de memoria). Aunque en este caso, también por una larga tradición C, y por tratarse de un puntero-a-cadena de caracteres, el compilador proporciona la cadena en vez del valor hexadecimal.

La expresión *(cpt->c) de la salida S.7 obtiene el valor señalado por el puntero. En este caso el primer elemento de la cadena. 

Tema relacionado:
  • Punteros a miembros de clases implícitas ( 4.12.2).

  Inicio.


[1] Aunque formalmente esta expresión no es una definición, ver al respecto: Declaraciones y definiciones ( 4.1.2).

[2] Una discusión más extensa sobre el tema en el apartado "Definición de miembros estáticos" ( 4.11.7)

[3]  Intente verbalizar una justificación de este nuevo resultado que le resulte convincente.