Está en la página 1de 65

4.2.

1d Puntero genrico
1 Sinopsis La declaracin: void* vptr;, formalmente: "puntero-a-void", declara que vptr es un puntero genrico, capaz de recibir sin problema el valor de cualquier puntero-a-tipoX (incluso nulo). Significa que pueden apuntar a objetos de cualquier tipo (con las excepciones sealadas ). Nota: la propia existencia de un puntero de estas caractersticas, que podramos denominar de "manga ancha", y que acepta albergar la direccin de objetos de cualquier tipo, parece ir contra las rigideces en el tratamiento de los tipos de C++ ("Strong type checking" 2.2). Hay que entender que se trata de otro de los legados del C clsico, aceptado por C++ para poder compilar el cdigo de su ancestro. No confundir un puntero genrico (a void) que aqu se describe, con el puntero nulo ( 4.2.1)

2 Ejemplo: int x = 1; float r = 1.0; void* vptr = &x; int main () { *(int *) vptr = 2; vptr = &r; *(float *)vptr = 1.1; }

// // // // // //

puntero-a-void actualmente sealando a int x ============= M.1: modifica el valor de x actualmente apuntando a float r M.3: modifica el valor de r

Observe el "casting" ( 4.9.9) en M.1 y M.3 para que el puntero-a-void pueda ser deferenciado ( 4.9.11a) para acceder a cada tipo de variable. Los punteros-a-void no pueden ser deferenciados sin un modelado explcito, porque el compilador no puede determinar el tamao del objeto al que referencian (este dato es imprescindible para una mnima aritmtica de punteros) [1].

3 Excepciones No obstante lo anterior, existen excepciones a la regla de libre asignacin a los punteros genricos. 3.1 No pueden asignrseles la direccin de una constante. Por ejemplo: int const x; void* p = &x;

// Error!

Para una asignacin de este tipo es necesario hacer:

void* p = (void*) &x; void const* p = &x; int const* p = &x;

// Ok: con un pequeo truco... // Ok: // Ok: Esta es la forma cannica

3.2 Los punteros-a-void tampoco pueden ser asignados a punteros-a-constante. Por ejemplo, siguiendo con el caso anterior: void* pv; pv = p; Observe que: int x = 10; void* vptr = &x; int* iptr = vptr; int* iptr = (int*) vptr;

// Error!

// Ok. // L.3: Error! // L.4: Ok.

La sentencia L.3 producira un error de compilacin: Cannot convert 'void *' to 'int *' in.... La razn es que a pesar de estar actualmente sealando la direccin de un int, para el compilador vptr es todava un puntero-a-void, por lo que su valor no puede ser asignado a un puntero-a-int. Son tipos diferentes y el compilador indica que l por su cuenta, no puede hacer la conversin necesaria. En L.4 se permite la asignacin porque previamente se ha "disfrazado" el tipo de vptr mediante un modelado adecuado. Forzamos explcitamente al compilador a hacer lo que de "motu proprio" no se ha atrevido.

3.3 Los punteros-a-funcin ( asignados a punteros-a-void.

4..2.4) y los punteros-a-miembro (

4.2.1g) no pueden ser

Ver el operador dynamic_cast ( 4.9.9c) para una discusin sobre la conversin (modelado) de punteros-a-clase en punteros-a-void.

4.2.1e Puntero constante/ a constante


1 Sinopsis Un puntero puede ser declarado con el modificador const; recurdese que cualquier entidad declarada con esta propiedad no puede modificar su valor ( 3.2.1c). En el caso de la declaracin de punteros, la posicin 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 anlogas; en ambos casos representan puntero-a-tipoXconstante (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 an a travs 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 sealan a sendos enteros cuyo Rvalue no pueden ser modificado, sin embargo, los punteros si pueden ser modificados. Por ejemplo, sealando a un nuevo objeto (siempre naturalmente que este sea un intconstante).

Los punteros-a-constante son de utilizacin ms frecuente que los punteros constantes (ver a continuacin). 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 sealan a objetos muy grandes y se desea evitar que la funcin invocada pueda modificar el objeto en cuestin. Ejemplo: class CMG {...}; void fun(const CMG*); // clase muy grande // funcin aceptando un puntero a la clase

int main() { // =========== CMG* aptr = new CMG; ... fun(aptr); // fun no puede modificar el objeto }

3 Puntero constante La expresin 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 declaracin. Es decir, debe ser una definicin del tipo: tipoX * const puntero = direccin-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 sealar al entero x durante toda su vida.

4 No confundir puntero constante con puntero-a-constante. Como puede verse en la ltima lnea del ejemplo que sigue, ambos casos pueden darse simultneamente (puntero constante a constante): int nN = 7; const int nC = 7; int *const pt1 = &nN; const int * pt2 = &nC; const int * const pt3 = &nC 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 // // // // // entero iniciado a 7 entero constante iniciado a 7 puntero constante a int puntero a constante tipo int puntero constante a constante tipo int

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 tambin tipos distintos. Ejemplos: const int kte = 75; int x = 75; int* ptr; // puntero-a-int ptr = &kte; // Error. La asignacin no es posible const int* ptk; // puntero-a-int-constante ptk = &kte; // Ok: Asignacin 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 asignacin, sera posible asignar al valor apuntado por pci (una constante) mediante asignacin a la direccin apuntada por pi.

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

3.2.1c )

5.1 A pesar a pesar de lo indicado anteriormente, es posible modificar una constante, de forma indirecta, a travs 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 instruccin sin error printf("%3i\n", edad); // L4: -> 40 printf("%3i\n", *(int *)&edad); // L5: -> 35 Comentario: El ejemplo, un tanto sorprendente, merece una explicacin: L1: La variable edad se define como constante-tipo-int. Se inicia al valor 40 L2: Se muestra su valor: 40 como cabra esperar. L3: Esta sentencia se ejecuta sin error. Modifica el valor de la constante de forma indirecta, a travs de un puntero, asignndole un valor de 35. La explicacin es que la direccin &edad, Lvalue(edad), que es la direccin de una constante-tipoint, es modificada por el operador de modelado(int *) que la promueve a puntero-a-int (aqu se pierde el carcter de constante); a este puntero lo denominaremos provisionalmente iptr. Finalmente, la expresin *iptr = 35 asigna 35 a la "variable" sealada 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 direccin iptr (la direccin de edad), y nos muestra el valor modificado, 35. La explicacin de esta aparente contradiccin es que (por razones de eficacia en la ejecucin del cdigo), en la lnea 4 el compilador utiliza el valor de la constante resuelta en tiempo de compilacin (se le haba dicho -Lnea 1- que era constante). El compilador supone que ser realmente constante y en el segmento de cdigo correspondiente a la sentencia de impresin pone directamente el valor 40 obtenido en tiempo de compilacin. Este es el valor que nos muestra en la lnea 4. Si embargo, cuando en la lnea 5 se le pide que muestre el verdadero valor (el que hay en la zona de almacenamiento de variables [1]) muestra el valor modificado.

4.2.1f Punteros a clases


1 Sinopsis Como prembulo al tema, no estaran de ms dos pequeas puntualizaciones: en primer lugar, recordar que para esta, como para muchas otras cuestiones relacionadas con las clases, es importante pensar en ellas como tipos de objetos. Tipos nuevos, definidos por el usuario, pero que en lo dems no se distinguen gran cosa de otros tipos preconstruidos en el lenguaje. Por ejemplo de la clase de los enteros int. En segundo lugar, advertir que el objeto x de una clase X, tiene existencia en memoria en forma de una estructura que contiene los miembros no estticos de la clase. La direccin del objeto es la del primer miembro de esta estructura, y las direcciones del resto de miembros se pueden calcular considerando desplazamientos respecto a esta direccin inicial [1]. Esta circunstancia es fundamental para entender el funcionamiento de los punteros-a-clase que tratamos aqu, y de los punteros-a-miembro, que tratamos en el prximo captulo ( 4.2.1g). Dicho esto, aadir que los punteros a clases no se diferencian de los que hemos considerado genricamente como punteros a objetos ( 4.2.1), aunque presentan algunas peculiaridades que conviene conocer [2].

2 Declaracin Del mismo modo que la forma genrica de puntero a la clase int es tipo int*, y podemos declarar un puntero a cualquier elemento de dicho tipo como: int* ptri; // ptri es puntero genrico a tipo int

la forma genrica de puntero a la clase C es C*, y consecuentemente, podemos declarar un puntero a cualquier objeto de la case como: C* ptrc; // ptrc es puntero genrico a tipo C

ms tarde, en uno y otro caso podemos iniciar dichos punteros a instancias concretas. Por ejemplo: int x = 5; ptri = &x; C c; ptrc = &c; // // // // iniciar asociar iniciar asociar un objeto x de ptri al objeto un objeto c de ptrc al objeto la clase int x la clase C c

Observe que en el caso de los enteros no es vlida la expresin: ptri = ∫ // Error

ya que int no es un objeto concreto de la clase. Por la misma razn tampoco es vlida la sentencia: ptrc = &C; // Error

En multitud de ocasiones los "objetos" normales (variables y constantes) se manipulan y referencian a travs de sus punteros (esta es justamente la razn de su existencia). La sintaxis, que se ha sealado anteriormente, utiliza el operador de indireccin ( 4.9.11a) en la forma: *ptri = 10; int y = *ptri; // Asignar el valor 10 a la variable x // y == x

Del mismo modo, las instancias de clases pueden referenciarse a travs de sus punteros. Para ilustrar su uso podramos utilizar una aproximacin paralela al caso anterior: class C { public: int x; } ... C c, d; C* ptrc = &c; C* ptrd = new C; (*ptrc).x = 10; d.x = (*ptrc).x;

// // // // //

objetos c y d, instancias de la clase C define ptrc puntero-a-tipoC sealando al objeto c L.7: Ok. el operador new devuelve un puntero L.8: Ok. c.x == 10 L.9: Ok. d.x == c.x

Observe que en L.7 instanciamos un objeto de la clase C y lo referenciamos mediante un puntero; al contrario que sus hermanos, c y d, este objeto no tiene etiqueta (nombre); en adelante deber ser accedido mediante el puntero ptrc (ms aclaraciones al respecto en operador new en 4.9.20). Aunque vlida, la sintaxis utilizada en L.8 y L.9 no es la ms elegante, ya que existe un operador especfico para referirse a miembros de objetos a travs de punteros a tales objetos: el selector indirecto -> ( 4.9.16). La notacin cannica de las dos ltimas lneas sera ( 4.11.2e): ptrc->x = 10; d.x = ptrc->x; // L.8bis: Ok. c.x == 10 // L.9bis: Ok. d.x == c.x

Como puede verse, ambas expresiones son equivalentes: ptrc->x = (*ptrc).x 3 Ejemplo: #include <iostream.h> class C { public: int x; int* p; char* c; void fun () { cout << "Valor miembro x == " << x << endl; } C () { // L.7: constructor por defecto x = 13; p = &x; c = "AEIOU";

} }; void f1 (C* cpt); int main (void) { C c1; C* cptr; cptr = &c1;

// L.13: prototipo de funcin normal (no-miembro) // // // // ======================== instancia de C puntero a clase asignado al objeto c1

cout << "1 c1.x == " << c1.x << endl; // Salida-1 cout << "2 c1.p == " << *c1.p << endl; // Salida-2 cout << "3 c1.c == " << c1.c << endl; // Salida-3 c1.fun(); // Salida-4 f1(cptr); // L.24: El puntero se utiliza como argumento de f1 } void f1 (C* cp) { cout << "4 c1.x cout << "5 c1.x cout << "6 c1.p cout << "7 c1.p cout << "8 c1.c cout << "9 c1.c (*cp).fun(); cp->fun(); } Salida: 1 c1.x == 13 2 c1.p == 13 3 c1.c == AEIOU Valor miembro x == 13 4 c1.x == 13 5 c1.x == 13 6 c1.p == 13 7 c1.p == 13 8 c1.c == AEIOU 9 c1.c == AEIOU Valor miembro x == 13 Valor miembro x == 13 Comentario: La clase del ejemplo tiene cuatro miembros pblicos: entero; puntero-a-int; puntero-a-char y una funcin. Tambin se ha incluido un constructor por defecto que inicializa adecuadamente los objetos creados. Adems de la clase, en el espacio global se ha definido una funcin f1, que acepta como argumento un puntero a la clase. En la funcin main hemos utilizado un puntero genrico a clase, cptr, que es asignado a una instancia concreta, c1. Las cuatro primeras salidas se deben a sentencias en la funcin main, y corresponden a invocaciones mediante el selector directo a miembro ( 4.9.16); en las tres primeras los miembros son propiedades (variables). En la cuarta el miembro es un mtodo (funcin) del objeto c1, que es invocado. == == == == == == " " " " " " // definicin de funcin << (*cp).x << endl; << cp->x << endl; << *(*cp).p << endl; << *cp->p << endl; << (*cp).c << endl; << cp->c << endl;

A continuacin, en L.24 se utiliza el puntero cptr como argumento de la funcin f1. Ya dentro de dicha funcin, se invocan las propiedades y mtodos del objeto c1 (sealado por el puntero), utilizando los dos formatos posibles. Observe atentamente la notacin utilizada en las sentencias del cuerpo de f1 para obtener los valores sealados por los punteros; todas ellas son muy ilustrativas.

4 Punteros en jerarquas de clases Cuando se definen punteros a clases-base, de las que derivan otras, se cumplen los siguientes principios: Los objetos de las clases derivadas pueden tratarse como si fuesen objetos de sus clasesbase cuando se manipulan mediante punteros y referencias. Un puntero de una clase-base puede contener direcciones de objetos de cualquiera de sus clases derivadas.

Cuando se acceden a travs de punteros objetos de clases que pertenecen a una jerarqua, es importante tener en cuenta las precauciones indicadas en "Consideraciones sobre punteros en jerarquas de clases" ( 4.11.2b1). Nota: en determinadas circunstancias el compilador puede realizar una conversin automtica de un puntero-a-clase-derivada a puntero-a-clase-base. Ver: Congruencia estndar de argumentos ( 4.4.1a)

Tema relacionado: Punteros a clases implcitas ( 4.12.2).

4.2.1g Punteros a miembros-de-clases


Advertencia didctica: la lectura de este captulo exige un conocimiento previo de las clases ( 4.11). Adems, puesto que los punteros-a-miembros de clases incluyen los punteros-a-mtodos, que son un tipo de funcin, aconsejamos al lector novel que antes de entrar en este captulo, repase con detenimiento el apartado relativo a punteros-a-funcin ( 4.2.4). Posteriormente, al volver aqu comprobar que los punteros-a-mtodos son una variedad de aquellos.

1 Sinopsis Adems de punteros genricos a clases ( 4.2.1f), tambin es posible establecer punteros a miembros concretos (propiedades y mtodos). Para establecer un cierto paralelismo, recordemos que la declaracin genrica de puntero-a-tipoX adopta la forma tipoX*. As, para declarar un puntero-a-entero se utiliza la expresin: int* ptri; // declara puntero-a-int

Posteriormente esta variable puede ser inicializada recibiendo un valor que es la direccin del objeto sealado. Por ejemplo: int x = 5; ptri = &x;

Del mismo modo, los punteros a miembro-tipoX-de-claseC, definen un tipo de objeto (constante o variable) que sirve para alojar las direcciones de miembros de instancias de la clase [ 1]. La declaracin genrica de puntero a miembro tipoX-de-claseC adopta la forma tipoX C::*. A su vez, para iniciar un puntero a miembro es preciso aplicar el operador de referencia & ( 4.9.11) al nombre cualificado ( 4.1.11c) del miembro. En nuestro caso, la direccin cualificada de un miembro m de C es &C::m. Nota: debe recordar que la asignacin a miembros no est permitida en el cuerpo de la clase ( 4.11.2a) y que la asignacin a punteros debe seguir las mismas reglas que para el resto de miembros ( 4.11.2d3). Recordar tambin que no es posible definir punteros a losconstructores o destructores de clase, ya que no es posible obtener las direcciones de este tipo de miembros ( 4.11.2d).

2 Ejemplos A continuacin se muestran algunos ejemplos de declaraciones de punteros a miembro-de-clase int C::* ptr; int* C::* ptr; char* C::* ptr; void (C::* fptr)(int); int (C::* fptr) (); char const * (C::* fptr)(); int (C::* fptr())(); Puntero a miembro de C que es un int. Puntero a miembro de C que es un puntero-a-int. Puntero a miembro de C que es un puntero-a-char. Puntero a funcin miembro (mtodo) de C que recibe un int y no devuelve nada. Puntero a funcin miembro de C que no recibe argumentos y devuelve un int. Puntero a funcin miembro de C que no recibe argumentos y devuelve un puntero-a-char constante. Puntero a funcin miembro de C que no recibe argumentos y devuelve un puntero a funcin que no recibe argumentos y devuelve un int.

3 Observaciones Recordemos que los punteros desempean un papel fundamental en C++. Precisamente, algunas de las construcciones ms elegantes y sofisticadas que pueden realizarse en l, los utilizan en sus diversas formas: punteros a objetos bsicos (a tipos predefinidos en el lenguaje 2.2); punteros a funciones; punteros a clases, y a miembros de estas (propiedades y mtodos). Sin embargo, como veremos a continuacin, estos ltimos (junto con los punteros-a-funcin) son quizs uno de los puntos de sintaxis C++ ms compleja (por supuesto, los punteros-a-miembro incluyen punteros-a-

funcin-miembro). A esto se suma el hecho de que los punteros a miembro requieren, sobre todo para el principiante, de un esfuerzo especial hasta lograr construir una imagen mental adecuada del mecanismo que los sustenta. El propio creador del lenguaje opina que son un punto oscuro: " ... pointers to members (which I consider an obscure corner of C++) " [2]. Cuando se trata de punteros a miembros de clase, existen varios puntos a los que aconsejamos prestar especial atencin: Los punteros a miembros estticos ( 4.11.7) tienen consideracin distinta que los punteros a miembros no-estticos. Esto se debe a que cada instancia de la clase contiene un subconjunto de los miembros no estticos, mientras que solo existe una instancia de los miembros estticos, que es comn para todos los objetos de la clase, por lo que estos miembros se parecen ms a objetos normales de un subespacio que a miembros de clase. Esta diferencia nos obliga a dividir el estudio de estos punteros en dos subapartados: o Punteros-a-miembros normales (

4.2.1g1)

o Punteros-a-miembros estticos ( 4.2.1g2) Las declaraciones utilizan una notacin un tanto especial; Stroustrup hace referencia a ellas sealando "the lack of readability of the C declarator sintax ", por lo que es muy frecuente la utilizacin de typedef ( 3.2.1a) en ellas. A lo largo de los ejemplos anteriores habr observado que la notacin utilizada para la declaracin de punteros a propiedades es un tanto particular, pues se aparta de la que venimos utilizando. En cambio, la notacin de punteros a mtodos es similar a la utilizada para punteros a funciones normales ( 4.2.4), aunque con el aadido de utilizar el operador de acceso a mbito :: ( 4.9.19).

La asignacin se realiza de forma genrica (sobre miembros de clase), no sobre objetos concretos (miembros de instancia) como ocurra en el caso de punteros a clases. Aunque lgicamente la utilizacin de estos punteros se realiza sobre instancias concretas de la clase (ver ejemplo 4.2.1.g1). La notacin utilizada para la invocacin de los valores sealados por estos punteros es la que cabra esperar. Como de costumbre, hay que aplicar la indireccin al puntero *mptr, aunque adems hay que aplicar el selector directo (.) o indirecto (->) para determinar a qu instancia concreta pertenece el miembro. As pues, si mptr es un puntero-a-miembro de un objeto obj, y optr es un puntero-alobjeto, el miembro sealado por mptr viene determinado por las expresiones: obj.*mptr y optr->*mptr. Es decir, el operador de indireccin * ( 4.9.11) sigue al selector directo . ( 4.9.16) o indirecto -> ( 4.9.16) de miembro segn el caso. Ver ejemplos en las pginas siguientes.

Recordar que el puntero-a-void no puede ser utilizado para sealar a un miembro ( 4.2.1d)

4 Sobre el rendimiento Como consecuencia del mecanismo relativamente complicado que debe utilizarse, la utilizacin de funciones-miembro a travs de punteros implica una gran penalizacin en trminos del rendimiento

global de la aplicacin. En especial si estos accesos se realizan en bucles muy repetitivos y/o se trata de funciones virtuales ( 4.11.8a), por lo que deberan evitarse en la medida de lo posible.

Temas relacionados: Punteros a clases implcitas ( 4.12.2). La indireccin de punteros a clases y a miembros (

4.9.11)

5 Bibliografa Christopher Skelly "Powerful Pointers To Member Functions" C/C++ Users Journal Octubre 1994

4.2.1g1 Punteros a miembros normales ( no-estticos)


Nota: a lo largo del presente captulo trataremos los aspectos de notacin con el mayor detalle, aunque podemos adelantar que, al constituir las clases un subespacio o mbito de nombres con ciertas peculiaridades, el manejo de punteros a sus miembros necesariamente debe tener en cuenta esta circunstancia, por lo que su notacin es muy parecida a la utilizada para acceder a miembros de subespacios ( 4.1.11c). Tambin es importante sealar que los punteros-a-miembros no estticos no pueden ser accedidos fuera de su espacio de direcciones.

1 Introduccin En principio, si consideramos que un miembro de clase (variable o funcin) es como otro objeto cualquiera del universo C++, la idea de un puntero que seale a una de estas entidades, no debera ser especialmente difcil. Sin embargo, veremos que el concepto implica algunas singularidades que intentaremos mostrar en el siguiente ejemplo: class C { int i; // int* mpi; // C(int n) { // i = n; mpi = &i; // } }; ... int C::* pmi = &C::i;

miembro entero miembro puntero-a-entero constructor el puntero seala a un miembro

// puntero-a-miembro entero-de-C.

Aqu existen sendos objetos que son punteros-a-miembro: el primero, mpi, es un puntero-amiembro; a su vez l mismo es miembro de la clase. A este tipo lo denominamos puntero interno, para distinguirlo de los que como pmi, son tambin punteros-a-miembro pero no pertenecen a la clase, y que denominamos externos. Si observamos la notacin empleada en este ltimo, puede comprobarse que pmi es un puntero-a-entero de un tipo especial; destinado a sealar no a un entero cualquiera, sino a un int del "subespacio" C. Observe que pmi no se inicia con ninguna

propiedad de instancia concreta (del tipo &cj.i), sino de forma genrica, con la direccin de una propiedad de clase (&C::i). Llegados a este punto cabra preguntarse: puesto que pmi es un objeto nico, no es una matriz de punteros, ni tampoco un miembro de clase como mpi Como puede sealar en un momento dado a la propiedad c1.i y al mismo tiempo a la propiedad c2.i? Desde luego parece una situacin paradjica que podramos sintetizar en el siguiente cdigo: C c1 = C(1); C c2 = C(2); // Cual es ahora el valor *pmi 1 o 2? La solucin al interrogante, y base para comprender el mecanismo de estos punteros, estriba en la propia estructura interna de los objetos C++. En realidad las instancias de clase son una especie de estructuras (en el sentido C del trmino) en las que no estn presentes las funciones miembro ni las variables o constantes estticas (que tienen su existencia en el objeto-clase 4.11.5). En el caso ms general, en que se heredan propiedades de otros ancestros ( 4.11.2c), un objeto est constituido por la yuxtaposicin de subobjetos que contienen las propiedades heredadas de sus bases directas mas sus propios miembros privativos si los hubiere. La figura 1muestra la disposicin en memoria de varios objetos cuando una clase D deriva de otra C que deriva a su vez de una superclase B: class B { /* ... */ }; class C : public B { /* ... */ }; class D : public C { /* ... */ }; ... B b; // Objeto B C c; // Objeto C D d; // Objeto D

Fig. 1 El objeto D es una zona contigua de memoria que comienza en un punto sealado por el puntero this ( 4.11.6). Cada subobjeto empieza en un punto, cuyo desplazamiento delta, es conocido por el compilador. A su vez, cada miembro de un subobjeto, incluyendo el subobjeto dominante (parte privativa de D), ocupa una posicin sealada por un desplazamiento offset respecto al anterior, que tambin es conocido por el compilador.
Esto hace que los punteros a propiedades no-estticas no almacenen una direccin determinada de memoria, como es el caso en los punteros a variables "normales". En realidad, un puntero como pmi a una propiedad i de una clase C, contiene un valor que es el desplazamiento delta + offset del miembro i respecto de la estructura de elementos que comienza en el punto sealado por this. En consecuencia, no tiene sentido hablar del objeto sealado ( *pmi ) o del valor ( pmi ) aislado, y los punteros a miembros no estticos no son considerados como autnticos punteros a objetos [1], ya que solo tienen significado cuando se refieren al miembro de un objeto c especfico *(c.p), en cuyo caso el compilador obtiene la direccin concreta, sumando el desplazamiento p = delta + offset del miembro a la direccin this de inicio del objeto.

Nota: en la pgina adjunta se aaden algunos comentarios adicionales sobre el particular ( Nota) Este artificio es ejecutado automticamente por el compilador, de forma que parece "como si" existiera una versin de pmi para cada instancia de la clase, aunque a costa de una cierta complejidad en el mecanismo subyacente. Complejidad que se incrementa extraordinariamente en los casos de herencia simple o mltiple, que se traduce en una menor eficiencia del cdigo resultante. Puede afirmarse que, exceptuando cuestiones de herencia, el comportamiento de los punteros-amiembro internos y externos es muy parecido, por no decir idntico. Con la diferencia de que el puntero interno-a-miembro es una propiedad de la clase con todos los derechos, mientras que el externo se comporta como tal, pero no pertenece a la clase.

En el ejemplo anterior seran posibles expresiones como c1.mpi y *(c1.mpi), mientras que un intento similar con punteros externos (del tipoc1.pmi) conduciran a un error de compilacin: 'pmi' is not a member of 'C' in.... En este caso, la sintaxis correcta sera c1.*pmi. Estas expresiones pueden interpretarse como sigue: c1.mpi Valor del miembro mpi del objeto c1. Puesto que mpi es un puntero, contiene una direccin de memoria.

*(c1.mpi) Contenido de la direccin de memoria sealada por el puntero-miembro. Puesto que es un puntero-a-miembro, contiene el valor de otro miembro de la instancia c1. c1.pmi Error. Expresin no permitida: pmi no es miembro de C. Recuerde que el selector directo de miembro . es un operador binario que exige que el operando situado a la derecha sea miembro de la clase, estructura, o unin sealada por el operando de la izquierda. Contenido de la direccin de memoria obtenida sumando el desplazamiento mpi a la direccin del objeto c1. Es el valor de un miembro del objeto c1.

c1.*pmi

Como colofn de lo anterior, podemos resumir que existen dos tipos de punteros-a-miembros no estticos: internos ( 4.2.1g1-2) y externos ( 4.2.1g1-3). En ambos casos la funcionalidad es idntica, aunque la mecnica de funcionamiento es totalmente distinta (distinta tambin segn que las entidades sealadas sean propiedades o mtodos). Aunque el funcionamiento interno es controlado por el compilador de forma transparente, veremos a continuacin que el programador debe tener en cuenta algunas diferencias sintcticas en la utilizacin de unos y otros.

2 Declaracin La declaracin de punteros-a-miembros no estticos sigue las reglas que cabra esperar, considerando que referencian objetos del subespacio determinado por la clase. La sintaxis de declaracin es la siguiente:

<tipo_objeto> <nombre_clase>::* <etiqueta_puntero>; <tipo_objeto> es el tipo de miembro al que sealar el puntero. <nombre_clase> es la clase a que pertenece el miembro sealado <etiqueta_puntero> es el identificador (nombre) del puntero. Ejemplo: class C { int x; int C::* iptr; // declara puntero-a-miembro C (int p=0) { // constructor x = p; iptr = &C::x; // inicia puntero } } int C::* iptr = &C::x; // declara e inicia puntero

En el ejemplo se han declarado dos punteros-a-miembro; uno es interno y el otro externo. Observe que no existe colisin de nombres porque ambos pertenecen a espacios distintos. Observe tambin que el miembro C::iptr no puede ser iniciado en el mismo punto de su declaracin, debe hacerse en el constructor. Conviene recordar aqu lo indicado al tratar de la declaracin de punteros ( 4.2.1a) donde sealamos que existen tantos tipos de punteros como tipos de objetos pueden ser sealados. Por ejemplo: int* iptr; char* cptr; los tipos de iptr y cptr son distintos (int* y char* respectivamente). Con los punteros-a-miembro ocurre lo mismo, con la salvedad de que dos punteros sealando al mismo tipo de miembro de dos clases distintas, tienen tipos distintos. Por ejemplo: int A::* ipt1 = &A::x; int B::* ipt2 = &B::x; los tipos de ipt1 y cpt2 son distintos (son respectivamente int A::* y int B::*). Por supuesto, el valor devuelto al deferenciar ambos punteros sera un int en los dos casos.

4.2.1g1-2 Punteros internos a miembros normales ( no-estticos)


I think all C++ tutorials should be required by law to include a special page: At the top of this page would be a large icon of a chain saw. Underneath, in 24point block type it should say, "Warning: The Software General of the United States has determined that putting pointers in C++ classes can be hazardous

to your health unless you fully understand Constructors, Destructors, Copy Constructors, and Assignment Operators". David Weber "Two C++ tutorials" C/C++ Users Journal. Marzo 1996.

3 Punteros internos Ilustraremos este apartado con sendos ejemplos, en los que se aprecia la sintaxis utilizada en la declaracin; asignacin, y posterior referencia para su uso de punteros internos (uso que generalmente se concreta en acceder al miembro -propiedad o mtodo- sealado). Observe con especial atencin las formas de declaracin y asignacin de los estos punteros, as como la invocacin de los referentes (miembros correspondientes) a travs de ellos.

3.1 Ejemplo-1 El programa solo pretende mostrar las posibilidades sintcticas y notacionales de este tipo de punteros, por lo que es extremadamente simple. La clase incluye dos funciones: fun y fan; dos enteros y una matriz de caracteres. El resto de miembros son punteros a objetos de distinto tipo. Algunos de estos punteros sealan a miembros de la propia clase, aunque para mostrar las diferencias en la notacin, se incluyen tambin miembros que son punteros a objetos externos a la clase. Con objeto de mostrar que no hay posibilidad de confusin si se utilizan los descriptores adecuados, los nombres de los enteros y de las funciones miembro se han hecho deliberadamente iguales a las funciones y enteros del espacio global del fichero (que en este caso coincide con el del programa, dado que este ocupa un solo fuente). Se ha incluido tambin un constructor explcito que proporciona una inicializacin adecuada a los objetos. El cuerpo de main se reduce casi exclusivamente a una serie de salidas para ilustrar la forma de acceder a los punteros y a los objetos sealados por estos. Esta salidas utilizan todos los punteros y en algunos casos se realizan por duplicado, utilizando los dos objetos c1 y c2, que se instancian en M.1. Su objeto es mostrar las diferencias, en su caso, entre los valores correspondientes a ambos objetos.

#include <iostream> using namespace std;

// Ejemplo-1

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; char achar[5]; void fun (); void fan (); int* pint1; int C::* pint2; char (*pachar1)[5]; char (C::* pachar2)[5];

// // // // // // // //

miembro int miembro array de char miembro funcion (metodo) miembro funcion (metodo) L.16 miembro puntero-a-int L.17 miembro puntero-a-miembro-int L.18 miembro puntero-a-matriz de char 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; achar[0] = 'A'; achar[1] = 'E'; achar[2] = 'I'; achar[3] = 'O'; achar[4] = 'U'; pint1 = &::x; // L.25 pint2 = &C::x; // L.26 pachar1 = &::achar; // L.27 pachar2 = &C::achar; // L.28 pfe = &::fun; // L.29 pfm = &C::fun; // L.30 } }; void C::fun() { cout << "Funcion interna-1. x ==" << x << endl; } void C::fan() { cout << "Funcion interna-2. x ==" << x << endl; } 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 << "1.1- c1.pint1 == " << c1.pint1 << endl; //cout << "1.2- c1.pint2 == " << c1.pint2 M.4 ERROR cout << "2.1- *(c1.pint1) == " << *(c1.pint1) << endl; cout << "2.2- c1.*c1.pint2 == " << c1.*c1.pint2 << endl; cout << "3.1- c1.pachar1 == " << c1.pachar1 << endl; //cout << "3.2- c1.pachar2 == " << c1.pachar2 M.8 ERROR cout << "4.1- *(c1.pachar1) == " << *(c1.pachar1) << endl; cout << "4.2- c1.*c1.pachar2 == " << c1.*c1.pachar2 << endl; cout << "5.1- Invocar c1.pfe == "; c1.pfe(); cout << "5.2- Invocar c1.pfm == "; (c1.*c1.pfm)(); cout << "Algunas modificaciones runtime -----------------\n"; c2.pint1 = &y; // M.14 cout << "6.1- *(c1.pint1) == " << *(c1.pint1) << endl; cout << "6.2- *(c2.pint1) == " << *(c2.pint1) << endl; c2.pint2 = &C::y; // M.17 cout << "7.1- c1.*c1.pint2 == " << c1.*c1.pint2 << endl; cout << "7.2- c2.*c2.pint2 == " << c2.*c2.pint2 << endl; c1.pfe = &fan; // M.20 cout << "8.1- Invocar c1.pfe == "; c1.pfe(); cout << "8.2- Invocar c2.pfe == "; c2.pfe(); c1.pfm = &C::fan; // M.23 cout << "9.1- Invocar c1.pfm == "; (c1.*c1.pfm)(); cout << "9.2- Invocar c2.pfm == "; (c2.*c2.pfm)(); return 0; } Salida: 1.12.12.23.14.14.25.1c1.pint1 == 0041A178 *(c1.pint1) == 103 c1.*c1.pint2 == 10 c1.pachar1 == 00420670 *(c1.pachar1) == aeiou c1.*c1.pachar2 == AEIOU Invocar c1.pfe == Funcion externa-1

5.2- Invocar c1.pfm == Funcion Algunas modificaciones runtime 6.1- *(c1.pint1) == 103 6.2- *(c2.pint1) == 301 7.1- c1.*c1.pint2 == 10 7.2- c2.*c2.pint2 == 26 8.1- Invocar c1.pfe == Funcion 8.2- Invocar c2.pfe == Funcion 9.1- Invocar c1.pfm == Funcion 9.2- Invocar c2.pfm == Funcion Comentario:

interna-1. x ==10 -----------------

externa-2 externa-1 interna-2. x ==10 interna-1. x ==13

Nota: respecto a la utilizacin de estos ejemplos con el compilador MS VC++ 6.0 ver " Notas particulares" ( 4.2.1g1.3) En las lneas L.16/21 del cuerpo de la clase se realiza la declaracin de los miembros que son punteros. Las declaraciones L.16/18/20 no ofrecen ninguna peculiaridad, se declaran miembros que son punteros a objetos normales (no son miembros de clase) y su declaracin es idntica a la que se usara con punteros del espacio global. En cambio, s merece destacarse la sintaxis utilizada en L.17/19/21 donde se declaran miembros que son punteros-a-miembros. Las asignaciones se realizan en las lneas L25/30 del constructor. Ntese que las asignaciones L.25/27/29 de los punteros que sealan a objetos "normales", utilizan el operador :: de resolucin de mbito sin ningn prefijo para referirse al espacio global de nombres ( 4.1.11c). La asignacin de L.30 es digna de mencin, pues utiliza explcitamente el identificador cualificado ( 4.1.11c) del mtodo cuya direccin se asigna al puntero. Pemos afirmar que se trata de una verdadera excepcin en la sintaxis y semntica del lenguaje. No se cumple la regla general de que el identificador de la funcin es equivalente a su direccin ( 4.2.4a). Es decir, la expresin pfm = &C::fun; // L.30

no puede ser sustituida [5] por pfm = C::fun;

Las salidas 1.1 y 3.1 muestran el valor de los miembros pint1 y pachar1 del objeto c1, utilizando el selector directo de miembro . sobre el objeto. Por ejemplo: c1.pint1. Puesto que ambos son punteros, estos valores son direcciones de memoria y como es tradicin en C++, se muestra en hexadecimal ( 2.2.4b). Observe que un intento anlogo para mostrar los valores de los punteros-a-miembros. Por ejemplo, c1.pint2, conduce a error [4] (lneas M.4 y M.8). Los grupos de salidas 2 y 4 muestran el valor de los objetos sealados por los punteros. Es muy notable la diferente sintaxis empleada segn se trate de un puntero a objeto externo o a miembro: *(c1.pint1) c1.*c1.pint2

En mi opinin, uno de esos casos que justifican quizs la "mala fama" del C++ [1]. Observe que esta diferencia se mantiene en todas las expresiones de salida que utilizan el valor del puntero, incluyendo las invocaciones a funciones-miembro (salidas 5, 8 y 9). Las salidas 5.1, 8.1 y 8.2, corresponden a invocaciones de las funciones externas a travs de sus punteros. La nica singularidad a resaltar aqu es que la invocacin utiliza los identificadores de los punteros en sustitucin de los identificadores de las funciones ( 4.2.4b). Recuerde que las siguientes sentencias son equivalentes ( Nota): cout << "Invocar c1.pfe == "; c1.pfe(); cout << "Invocar c1.pfe == "; (*c1.pfe)(); cout << "Invocar c1.pfe == "; (*(c1.pfe))();

Su contrapartida son las salidas 5.2, 9.1 y 9.2, que representan la invocacin de las funcionesmiembro mediante sus punteros. Tenga en cuanta que en este caso (c1.*c1.pfm)(); es la simplificacin de (c1.*(c1.pfm))(); una vez eliminado el parntesis interno, y no es equivalente a c1.*c1.pfm();: cout << " Invocar c1.pfm == "; (c1.*c1.pfm)(); cout << " Invocar c1.pfm == "; c1.*c1.pfm(); // Ok. // ERROR!!

Nota: la eliminacin del parntesis interior es posible porque el operador deferencia de punteros-a-miembros de clase .*, tiene una precedencia menor que la del selector directo .. Las sentencias M.14/17/20/23 muestran la forma de modificar estos miembros de objetos (punteros). Las salidas que les siguen muestran el resultado de los cambios. Es digno de mencin que un valor como c2.pint2 no pueda ser mostrado ni almacenado , sin embargo si puede ser asignado: c2.pint2 = &C::y; // M.17

3.2 Ejemplo-2 Presentamos aqu una versin prcticamente idntica al ejemplo anterior, aunque con pequeas modificaciones. Las salidas son bsicamente las mismas, pero se han trasladado desde el cuerpo de main a una funcin auxiliar fs, ya que se pretende mostrar la sintaxis cuando el acceso se realiza a travs de un puntero a la clase (en realidad un puntero al objeto). Observe que algunos accesos utilizan una doble indireccin: primero se accede al objeto a travs de un puntero; despus se accede al miembro a travs de otro.

#include <iostream> using namespace std;

// Ejemplo-2

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; } 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); // M.2 fs(pc1); return 0; } void fs(C* cpt) { cout << "1.1- cpt->pint1 == " << cpt->pint1 << endl; //cout << "1.2- cpt->pint2 == " << cpt->pint2; ERROR cout << "2.1- *(cpt->pint1) == " << *(cpt->pint1) << endl; cout << "2.2- cpt->*cpt->pint2 == " << cpt->*cpt->pint2 << endl; cout << "3.1- cpt->pachar1 == " << cpt->pachar1 << endl; //cout << "3.2- cpt->pachar2 == " << cpt->pachar2; ERROR cout << "4.1- *(cpt->pachar1) == " << *(cpt->pachar1) << endl; cout << "4.2- cpt->*cpt->pachar2 == " << cpt->*cpt->pachar2 << endl; cout << "5.1- Invocar cpt->pfe == "; cpt->pfe(); cout << "5.2- Invocar cpt->pfm == "; (cpt->*(cpt->pfm))(); cout << "Algunas modificaciones runtime -----------------\n"; cpt->pint1 = &y; // FS.12 cout << "6.1- *(cpt->pint1) == " << *(cpt->pint1) << endl; cpt->pint2 = &C::y; // FS.14 cout << "7.1- cpt->*cpt->pint2 == " << cpt->*cpt->pint2 << endl; cpt->pfe = &fan; // FS.16 cout << "8.1- Invocar cpt->pfe == "; cpt->pfe(); cpt->pfm = &C::fan; // FS.18

cout << "9.1- Invocar cpt->pfm == "; (cpt->*cpt->pfm)(); } Salida: 1.1- cpt->pint1 == 0041A178 2.1- *(cpt->pint1) == 103 2.2- cpt->*cpt->pint2 == 10 3.1- cpt->pachar1 == 00420648 4.1- *(cpt->pachar1) == aeiou 4.2- cpt->*cpt->pachar2 == AEIOU 5.1- Invocar cpt->pfe == Funcion externa-1 5.2- Invocar cpt->pfm == Funcion interna-1. x ==10 Algunas modificaciones runtime ----------------6.1- *(cpt->pint1) == 301 7.1- cpt->*cpt->pint2 == 20 8.1- Invocar cpt->pfe == Funcion externa-2 9.1- Invocar cpt->pfm == Funcion interna-2. x ==10 Comentario: Las salidas se han trasladado desde main a la funcin fs, y son simtricas a las del ejemplo anterior. Observe que las instrucciones tambin guardan una simetra. Simplemente se ha sustituido el prefijo c1., objeto + selector directo de miembro ( 4.9.16) por el prefijo cpt->, puntero + selector indirecto ( 4.9.16). Incluso se han mantenido algunas sentencias que producen error, para recordar que no es posible obtener el valor de uno de estos punteros [4]. Recuerde que, en C++, ->* es un solo operador (indireccin de punteros a punteros a miembros) con precedencia menor que la del selector indirecto -> ( 4.9.0a), de forma que la salida 5.2 puede ser expresada mediante: cout << "5.2.- Invocar cpt->pfm == "; (cpt->*cpt->pfm)();

Observe tambin que al ser pfe un puntero-a-funcin, las invocacin que podramos llamar "ortodoxa" en las salidas 5.1 y 8.1 adoptara la forma: cout << "5.1.- Invocar cpt->pfe == "; (*(cpt->pfe))(); Sin embargo, por las razones ya comentadas, las que siguen son equivalentes: cout << "5.1.- Invocar cpt->pfe == "; (*(cpt->pfe))(); cout << "5.1.- Invocar cpt->pfe == "; (cpt->pfe)(); cout << "5.1.- Invocar cpt->pfe == "; cpt->pfe();

Es digno de mencin que el objeto instanciado en M.2 carece de identificador. En realidad solo se conoce su puntero pc1 (que es pasado como argumento a la funcin de salida fs). Todas las expresiones de acceso utilizadas en las salidas comienzan con una indireccin de este puntero para acceder al objeto; a continuacin se accede directamente al miembro. En los casos en que este es a su vez un puntero (salidas 2, 4, 6 y 7), se hace necesaria una nueva indireccin para acceder al objeto final.

Las asignaciones FS12/14/16/18 se corresponden con M14/17/20/23 del ejemplo anterior, y muestran la forma de modificar los miembros de instancia a travs del puntero al objeto.

3.3 Utilizacin de los miembros-puntero Los miembros de clases que son a su vez punteros, se prestan a las mismas tcnicas y aplicaciones que los punteros normales, aunque con el coste de eficiencia ya sealado ( 4.2.1g1). Entre los muchos ejemplos que podran plantearse, supongamos que una clase CTransporterepresenta el costo de envo de los productos de un fabricante. Deseamos obtener el costo mediante invocacin de uno de sus mtodos en funcin del peso total del envo, pero los mtodos utilizados varan. Por ejemplo, para menos de 1 Kg se utiliza correo ordinario; para 1 a 10 Kg. mensajera normal; para 10 a 20 Kg agencia de transporte exterior, y para ms de 20 Kg medios propios. Cada una de estas formas de envo tiene distintas formas de clculo de costo, por lo que utiliza mtodos distintos. Una posibilidad sera disear distintos mtodos que representaran las distintas formas de clculo de costo, utilizando un puntero-a-mtodo para acceder a la funcin correspondiente. El puntero es iniciado con la direccin del mtodo adecuado en el momento de la construccin del objeto en funcin la clase de envo: class CTransporte { private: float correo(float); float mensajeria(float); float agencia(float); float propio(float); public: float (CTransporte::* costo)(float); // puntero CTransporte(float); // constructor }; ... float CTransporte::correo (float peso) { /* ... */ }; float CTransporte::mensajeria (float peso) { /* ... */ }; float CTransporte::agencia (float peso) { /* ... */ }; float CTransporte::propio (float peso) { /* ... */ }; ... CTransporte::CTransporte(float peso) { if (peso < 1) costo = &CTransporte::correo; else if (peso < 10) costo = &CTransporte::mensajeria; else if (peso < 20) costo = &CTransporte::agencia; else costo = &CTransporte::propio; } float costoExp(float exp[], int items) { // calcula el costo de expedicion float cTot = 0; for (int item = 0; item < items; item++) { CTransporte ct(exp[item]); cTot += (ct.*ct.costo)(exp[item]); } return cTot; }

Comentario: La funcin costoExp recibe una matriz de float que contiene los pesos de los paquetes que componen una expedicin, y un int que indica el nmero de paquetes que la componen. El argumento items puede calcularse mediante la expresin: int items = sizeof exp / sizeof exp[0]; En la pgina adjunta se muestra una versin ejecutable del mismo ejemplo ( Ejemplo).

Punteros a miembros no estticos de clases


1 Ejemplo-1 Muestra de utilizacin de miembros-puntero. Es una versin ejecutable (compilable) del caso planteado en la pgina anterior, en el que una claseCTransporte representa el costo de envo de los productos de un fabricante, y deseamos obtener el costo mediante invocacin de uno de sus mtodos en funcin del peso total del envo, pero los mtodos utilizados varan. Por ejemplo, para menos de 1 Kg se utiliza correo ordinario; para 1 a 10 Kg. mensajera normal; para 10 a 20 Kg agencia de transporte exterior, y para ms de 20 Kg medios propios. Cada una de estas formas de envo tiene distintas formas de clculo de costo, por lo que utiliza mtodos distintos. La solucin adoptada consiste en disear distintos mtodos que representaran las distintas formas de clculo de costo, y la utilizacin de un puntero-a-mtodo para acceder a la funcin correspondiente. El puntero es iniciado con la direccin del mtodo adecuado en el momento de la construccin del objeto en funcin la clase de envo: #include <iostream> using namespace std; class CTransporte { private: float correo(float); float mensajeria(float); float agencia(float); float propio(float); public: float (CTransporte::* costo)(float); // puntero CTransporte(float); // constructor }; float CTransporte::correo (float peso) { cout << "Correo" << endl; return 4*peso; } float CTransporte::mensajeria (float peso) { cout << "Mensajeria" << endl; return 3*peso; } float CTransporte::agencia (float peso) { cout << "Agencia" << endl; return 2*peso; } float CTransporte::propio (float peso) { cout << "M Propios" << endl; return peso;

} CTransporte::CTransporte(float peso) { if (peso < 1) costo = &CTransporte::correo; else if (peso < 10) costo = &CTransporte::mensajeria; else if (peso < 20) costo = &CTransporte::agencia; else costo = &CTransporte::propio; } int main() { // ============== float exp[6] = {0.1, 2.2, 4.4, 8.8, 16.16, 32.32}; float cTot = 0; int items = sizeof exp / sizeof exp[0]; for (int item = 0; item < items; item++) { CTransporte ct(exp[item]); // M6: cTot += (ct.*ct.costo)(exp[item]); // M7: } cout << "Costo total: " << cTot << " Euros" << endl; return 0; } Salida: Correo Mensajeria Mensajeria Mensajeria Agencia M Propios Costo total: 111.24 Euros Comentario: Una forma equivalente de construir el bucle de clculo, sustituira las sentencias M6 y M7 por las siguientes: CTransporte* cPt = new CTransporte(exp[item]); cTot += (cPt->*(cPt->costo))(exp[item]); // M6bis // M7bis

2 Ejemplo-2 En cualquier caso, la solucin propuesta presenta un inconveniente importante: el bucle for construye tantos objetos CTransporte como bultos componen la expedicin, lo que supone un consumo considerable de memoria y tiempo de construccin de los objetos. Como solucin proponemos un diseo alternativo en el que la decisin del mtodo de clculo utilizado, se realiza en el cuerpo de un mtodo auxiliar costo() que es la interfaz de la clase con el exterior. Este mtodo sustituye al puntero del ejemplo anterior, que ahora pasa a denominarse pfcost. Como consecuencia, no es necesario definir un constructor explcito. Es suficiente con la versin por defecto proporcionada por el compilador.

#include <iostream> using namespace std; class CTransporte { private: float correo(float); float mensajeria(float); float agencia(float); float propio(float); float (CTransporte::* pfcost)(float); // puntero public: float costo(float); }; float CTransporte::correo (float peso) { cout << "Correo" << endl; return 4*peso; } float CTransporte::mensajeria (float peso) { cout << "Mensajeria" << endl; return 3*peso; } float CTransporte::agencia (float peso) { cout << "Agencia" << endl; return 2*peso; } float CTransporte::propio (float peso) { cout << "M Propios" << endl; return peso; } float CTransporte::costo(float peso) { if (peso < 1) pfcost = &CTransporte::correo; else if (peso < 10) pfcost = &CTransporte::mensajeria; else if (peso < 20) pfcost = &CTransporte::agencia; else pfcost = &CTransporte::propio; return (this->*(this->pfcost))(peso); } int main() { // ============== float exp[6] = {0.1, 2.2, 4.4, 8.8, 16.16, 32.32}; float cTot = 0; int items = sizeof exp / sizeof exp[0]; CTransporte ct; // un nico objeto for (int item = 0; item < items; item++) { cTot += ct.costo(exp[item]); } cout << "Costo total: " << cTot << " Euros" << endl; return 0; } La salida es exactamente anloga a la del ejemplo anterior. Comentario: El mtodo pblico costo() es ahora el encargado de iniciar el puntero pfcost con el valor del mtodo adecuado en funcin del peso del envo, y de invocarlo a continuacin utilizando el puntero.

Observe que en realidad, el mtodo costo() acta como "handle" o interfaz del algoritmo que realmente se encarga de la computacin.

4.2.1g1-3 Punteros externos a miembros normales ( no-estticos)


4 Punteros externos Recordemos que los punteros-a-miembros no estticos que hemos denominado "externos", son aquellos que no son miembros de la clase cuyos miembros se referencian. Para ilustrar su mecnica de uso utilizaremos sendos ejemplos que son una prolongacin 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 lneas 37 a 45 hemos incluido 9 punteros externos que sealan a los miembros de la clase (que ya nos son conocidos). Salvo los tres primeros (L.37/38/39) que sealan a un int, a una matriz de char y a un mtodo de la clase, los dems apuntan a miembros que son a su vez punteros. Es importante resear 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 anlogo a los anteriores. Se trata de un mero conjunto de ejemplos sintcticos relativos al uso de punteros-a-miembro. La funcin main solo incluye instrucciones de salida y unas pocas asignaciones, de forma que los objetos ya definidos adopten nuevos valores. #include <iostream> using namespace std; // Ejemplo-3

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; char achar[5]; void fun (); void fan (); int* pint1; int C::* pint2; char (*pachar1)[5]; char (C::* pachar2)[5]; de char void (* pfe)(); void (C::* pfm)(); C (int n = 0) { x = n; y = 2*n; pint1 = &::x; pint2 = &C::x;

// // // // // // // //

miembro int miembro array de char miembro funcion (metodo) miembro funcion (metodo) L.16 miembro puntero-a-int L.17 miembro puntero-a-miembro-int L.18 miembro puntero-a-matriz de char L.19 miembro puntero-a-miembro-matriz

// L.20 miembro puntero-a-funcion externa // L.21 miembro puntero-a-funcion miembro // constructor por defecto // L.24

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; } // definicin de punteros externos-a-miembro (p-a-m) int C::* iptr = &C::x; // L.37 p-a-m char (C::* acptr)[5] = &C::achar; // L.38 p-a-m void (C::* fptr)() = &C::fun; // L.39 p-a-m int* C::* pptr1 = &C::pint1; // L.40 p-a-m int C::* C::* pptr2 = &C::pint2; // L.41 p-a-m miembro int char (*C::* paptr1)[5] = &C::pachar1; // L.42 p-a-m de char char (C::*C::* paptr2)[5] = &C::pachar2; // L.43 p-a-m matriz de char void (*C::* pfptr1)() = &C::pfe; // L.44 p-a-m externa void (C::*C::* pfptr2)() = &C::pfm; // L.45 p-a-m miembro int matriz de char metodo puntero-a-int puntero-apuntero-a-matriz puntero-a-miembro puntero-a-funcion puntero-a-funcion

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; // cout << "18.1- Invocar c1.fan cout << "18.2- Invocar c2.fan *(c1.*pptr1) = 130; // c1.*(c1.*pptr2) = 310; // cout << "19.1- ::x == cout << "20.1- c1.x == **(c1.*paptr1) = 'f'; // (*(c1.*paptr1))[2] = 'r'; // *(*(c1.*paptr1)+4) = 'z'; // cout << "21.1- ::achar == *(c1.*(c1.*paptr2)) ='M'; // (c1.*(c1.*paptr2))[2]='L'; // *(c1.*(c1.*paptr2)+4)='N'; // cout << "22.1- c1.achar == return 0; } Salida:

M.31 == "; (c1.*fptr)(); == "; (c2.*fptr)(); M.34 M.35 " << *(c2.*pptr1) << endl; " << c1.*(c1.*pptr2) << endl; M.38 M.39 M.40 " << *(c1.*paptr1) << endl; M.42 M.43 M.44 " << c1.*(c1.*paptr2) << endl;

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 (lneas L37/45); en especial las declaraciones que corresponden a la parte izquierda (Lvalues) de las expresiones de asignacin. Por su parte los Rvalues no presentan ninguna singularidad. Como hemos sealado anteriormente ( 4.2.1g) nos limitamos a aplicar el operador de referencia & ( 4.9.11) al nombre cualificado ( 4.1.11c) del miembro.

Preste atencin a las declaraciones de L44 y L45 cuyas sintaxis quizs sean lgicas, pero desde luego no "evidentes". En especial, la declaracin del puntero-a-miembro pfptr2 y su posterior utilizacin para invocar a la funcin (L.45 y salida 15): void (C::*C::* pfptr2)() = &C::pfm; (c1.*(c1.*pfptr2))(); instancia // declaracin del puntero // invocacin del mtodo de

El cuerpo de main es muy parecido estructuralmente al ejemplo-1, de forma que pueden compararse las salidas de igual nmero para verificar la simetra que existe entre ambos. Las diferencias consisten en que aqu los objetos se referencian a travs de punteros externos. Observe que las expresiones utilizadas son muy similares. En el ejemplo anterior accedamos a los miembros de clase por el mtodo estndar, 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 indireccin 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 demostracin de que es errneo 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 indireccin ( 4.9.11) del puntero (c1.*iptr ). En la segunda salida el camino es un poco ms 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 invocacin de los mtodos de dos objetos a travs de su puntero. Lo verdaderamente notable es que se han obtenido valores diferentes para la versin 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 podran considerarse "casi" como autnticos miembros de la clase. Las sentencias de salidas 6 y 9 evidencian la distinta sintaxis para acceder a un elemento mediante un puntero externo segn el miembro referenciado sea puntero-a objeto-externo o puntero-a objeto-miembro (los parntesis de estas expresiones no son prescindibles). *(c1.*pptr1) c1.*(c1.*pptr2) // puntero-a-miembro que es puntero-a-int-externo // 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) c1.*(c1.*paptr2) // --> aeiou // --> AEIOU

en realidad contienen la direccin 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 tamao 5 y el objeto cout se encarga de mostrarlos en todo su contenido. Si aplicamos una indireccin adicional a estos valores, obtenemos el objeto sealado por el puntero. En este caso, el primer elemento de ambas matrices. **(c1.*paptr1) *(c1.*(c1.*paptr2)) // --> a // --> A

Las salidas 14 y 15 representan respectivamente la invocacin de una funcin ::fun y un mtodo c.fun a travs de puntero-a-puntero-a-funcin. En estas expresiones tampoco son prescindibles los parntesis. La parte de main que sigue a las modificaciones runtime contiene una serie de expresiones de asignacin muy interesantes. Muestran la sintaxis apropiada para modificar los objetos sealados por los punteros, as como algunas salidas para comprobacin de los nuevos valores. La sentencia M.22 realiza una nueva asignacin al puntero iptr que modifica la que se realiz en su definicin (L.37). A su vez la sentencia M.25 modifica el objeto (propiedad de instancia) sealado 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 ms interesante: representan la modificacin de elementos de una matriz de caracteres a travs de un puntero-a-miembro. A continuacin la salida 17 sirve de comprobacin de los cambios efectuados. *(c1.*acptr) = 'F'; (c1.*acptr)[2] = 'R'; *((c1.*acptr)+4) = 'Z'; // M.27 // M.28 // 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 modificacin del objeto sealado por el puntero. Puesto que c1.*acptr representa a achar, su indireccin representa al primer elemento "A" de la matriz, valor que es sustituido por "F". La sentencia M.29 representa la indireccin del "puntero" achar despus de sumarle cuatro unidades, lo que significa modificar el valor actual del ltimo carcter "U" de la matriz, por "Z". La sentencia M28 representa una variacin del anterior, con la diferencia de que en vez de utilizar la aritmtica de punteros, usamos la de subndices. Observe que estamos aplicando la notacin de subndices (de matrices) a un puntero. Observe tambin como esta expresin tiene un operador de indireccin * menos que cualquiera de las otras dos (que son equivalentes). Esto se debe a que "aplicar subndices a un puntero- a- matriz equivale a aplicarle el operador de indireccin" ( 4.3.2). La sentencia M.31 es muy parecida a M.22. Tambin aqu se asigna un nuevo valor a un punteroa-miembro externo fptr, que modifica el que se asign en su definicin (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 funcin anloga 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 ms 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 parntesis auxiliares en las sentencias 39 y 40.

4.2 Ejemplo-4 El caso que se presenta guarda con el anterior ( ) la misma relacin que el ejemplo 2 con el 1. Se mantiene la estructura con pequeas modificaciones y tambin aqu se han trasladado las salidas a una funcin auxiliar fs, para mostrar la sintaxis de acceso mediante un puntero a la clase.

#include <iostream> using namespace std;

// Ejemplo-4

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; pfm = &C::fun;

// L.28 // L.29

} }; void C::fun() { cout << "Funcion interna-1. x ==" << x << endl; } void C::fan() { cout << "Funcion interna-2. x ==" << x << endl; } // definicin 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 == " << cout << "20.1- c1.x == " << **(cpt->*paptr1) = 'f'; // (*(cpt->*paptr1))[2] = 'r'; // *(*(cpt->*paptr1)+4) = 'z'; // cout << "21.1- ::achar == " << *(cpt->*(cpt->*paptr2)) = 'M'; // (cpt->*(cpt->*paptr2))[2]= 'L'; // *(cpt->*(cpt->*paptr2)+4)= 'N'; // cout << "22.1- c1.achar == " << } Salida:

*(cpt->*pptr1) << endl; cpt->*(cpt->*pptr2) << endl; M.38 M.39 M.40 *(cpt->*paptr1) << endl; M.42 M.43 M.44 cpt->*(cpt->*paptr2) << endl;

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 fcil seguir la mecnica y notacin utilizadas. Aunque el procedimiento de obtencin 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 indireccin sobre su puntero cpt. A continuacin una nueva indireccin del puntero-a-miembro permite acceder a estos. Aparte de las asignaciones L.44 y L.45, ya sealadas en el comentario del ejemplo anterior, es tambin digna de mencin la sintaxis de invocacin del mtodo de instancia a travs de su puntero pfptr2 (salida 15): (cpt->*(cpt->*pfptr2))(); (teniendo expresiones as Quin 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 opcin de compilacin desconocida para mi. b Si en la inicializacin existente en L.25 para pint1 (miembro puntero-a-int) del Ejemplo 1 ( 4.2.1g1-2), se sustituye la direccin de la variable ::x del espacio global: pint1 = &::x; // L.25

por la direccin del miembro x: pint1 = &x; // L.25-bis OK!!

ninguno de los compiladores probados (BC++ y GNU Cpp) produce error, aunque supuestamente deberan indicarlo, ya que el tipo de x es distinto en ambos casos. Probablemente se trata de un error de ambos compiladores; de una conversin de tipo realizada automticamente por el compilador, o de una excepcin intencionada (podra argumentarse que a fin de cuentas ambos objetos son int). Sin embargo, la sustitucin inversa no es posible. Si en la inicializacin de L.26 para pint2 (miembro puntero-a-miembro-int) se sustituye el miembro C::x: pint2 = &C::x; por la variable global ::x pint2 = &::x; // L.26-bis ERROR!! // L.26

ambos compiladores "protestan" y generan un error indicando que la conversin de tipo no es imposible (el puntero no es del tipo adecuado). Desde luego, se trate de un error, o de un "casting" automtico, realizado en un caso s y en otro no, por el compilador, quizs sea este tipo de peculiaridades y alguna otra que veremos al tratar de los punteros-a-miembros estticos, 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 estn permitidas las asignaciones ( 4.11.2a). La situacin puede ser esquematizada como sigue: class C { int x; int C::* xptr = &C::x; }

// Ok. declaracin de miembro // ERROR!! Asignacin no permitida

La inicializacin [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 inicializacin es:

class C { int x; int C::* xPtr; C() { x = 0; xPtr = &C::x; } }

// declaracin de miembros // Constructor // Ok. asignacin correcta

Existe una excepcin cuando los miembros son funciones (mtodos). En este caso la declaracin y definicin pueden realizarse dentro del cuerpo de la clase, aunque la definicin tambin puede estar fuera: class C { int x; public: void putX(int a) { x = a; } int getX() {} } int C::getX(){ return x; }

// Ok. declaracin+definicin // Ok. declaracin (prototipo) // Ok. definicin externa

En ocasiones podra parecer que esto tambin es posible con los punteros-a-miembro. Considere el siguiente ejemplo: #include <iostream> using namespace std; class C { public: int x; int* xPtr; C(int a) { x = a; } } int C::* xPtr = &C::x;

// L.7: declaracin

// L.12: asignacin 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 utilizacin de xPtrproducira un error. Por ejemplo, aadiendo la siguiente sentencia a main, no se obtiene ningn error de compilacin, pero se produce un error fatal en runtime:

cout << " c1.x == " << *(c1.xPtr) << endl;

// M.4:

La explicacin a este "extrao" comportamiento, es que en realidad, la sentencia L.12 no es la inicializacin del miembro xPtr declarado en L.7, sino la declaracin de un nuevo puntero externoa-miembro en el espacio global: ::xPtr. De forma que el miembro C::xPtr queda sin inicializar; su valor es basura y seala a una posicin de memoria arbitraria. En M.3 se est utilizando el puntero externo ::xPtr, pero M.4 es la indireccin 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 direccin seala a un punto fuera del espacio asignado a la aplicacin.

4.2.1g2 Punteros a miembros estticos


1 Sinopsis Recordemos que, por las razones ya sealadas ( 4.2.1g), los punteros a miembros no-estticos no son considerados como autnticos punteros a objeto. Por contra, dado que los miembros estticos ( 4.11.7) se parecen ms 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 estticos, podramos hacer aqu una subdivisin de los casos que pueden presentarse con punteros p a miembros estticos m de una clase C: 1.- Punteros que son miembros de C (punteros internos a miembros estticos). 1.a.- Punteros que son miembros normales (no-estticos) 1.b.- Punteros que son miembros estticos 2.- Punteros que son objetos externos a la clase (punteros externos a miembros estticos).

2 Declaracin de Punteros a miembros estticos La primera observacin a tener en cuenta es que puede tomarse la direccin de miembros estticos si, y solo si, tales miembros tienen unadefinicin fuera de la clase. Es decir, solo pueden declarrseles punteros si se cumple dicha condicin. Ejemplo: class C { public: static const int k1 = 3; static const int k2 = 4; }; const int C::k1; ... int main () { const int* ptr1 = &C::k1; const int* ptr2 = &C::k2 ... }

// definicion // definicin [1]

// Ok. // Error!!

Comentario: respecto a este punto, el comportamiento de los compiladores difiere segn su grado de adaptacin al Estndar. Visual C++ 6.0 de MS no permite realizar definiciones de propiedades estticas 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 direccin de una de estas variables aunque no exista una definicin en el exterior (probablemente esto ltimo se deba a un error de este compilador). GNU Cpp 2.95 s parece adecuarse al Estndar.

2.1 Ejemplo-1 Para ilustrar el uso de punteros a miembros estticos y las caractersticas especiales de este tipo de miembros, comenzaremos por un caso sencillo que contempla la segunda hiptesis del epgrafe anterior (punteros a miembros estticos que son externos a la clase).

#include <iostream> using namespace std; class C { public: static int x; static int* p; static char* c; static void fun (); C () { x = 13; p = &C::x; c = "AEIOU"; } };

// Punteros externos a miembros estticos

// // // //

L.6 L.7 L.8 L.9 constructor por defecto

// L.11

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 genricos 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-carcter void (* fptr) (); // M.5 puntero-a-funcin // 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 travs 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 Valor Valor Valor c1.x == c1.x == c1.c == miembro 13 13 AEIOU x == 13

Comentario: A la luz de los expuesto en el captulo 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; ... p = &C::x; // L.6b miembro puntero-a-miembro-int // L.11 ERROR!!

se obtiene un error de compilacin al intentar posteriormente la asignacin 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 ms se pone de manifiesto que los miembros estticos 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 declaracin de estos como punteros-a-miembro tipoX-declaseC, 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 (lneas 15 a 17) que son necesarias en estos casos (2 ). Es importante observar que si estos miembros no hubiesen sido estticos, se habra obtenido un error de compilacin: Multiple declaration for 'C::x'.... Recuerde que C++ no permite definiciones mltiples ( y que la definicin de mtodos fuera del cuerpo de la clase (L.14) es siempre un caso especialmente autorizado. 4.1.2)

Las lneas M.2/5 definen cuatro punteros al estilo clsico que luego son asignados sin inconveniente a miembros de la clase (lneas M.7/10). A su vez las sentencias M.13/16 acceden a los objetos sealados al estilo tradicional, esto es, utilizando el operador * de indireccin ( 4.9.11) sobre el puntero. Recordar que M.16 puede ser sustituida por: (fptr)(); fptr(); // M.16b // M.16c

Es ilustrativo observar que en todo el programa no se ha utilizado para nada la instancia c1 de C, aunque su mera declaracin ha influenciado en los valores de salida por obra y gracia del constructor, pues su invocacin ha modificado los valores de inicio de los miembros

estticos [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 Valor Valor Valor c1.x == c1.p == c1.c == miembro 3 3 aeiou x == 3

2.2 Ejemplo-2 Para completar, ofrecemos una versin simtrica de la anterior pero aadiendo un puntero-aobjeto.

#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"; // definicin 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) cout << "S.1 c.x cout << "S.2 c.x cout << "S.3 c.c cout cout //cout cout cout } << << << << << "S.4 "S.5 "S.5 "S.6 "S.7 // ========================

{ == == ==

// funcin auxiliar " << *xptr << endl; " << **pptr << endl; " << *cptr << endl; << cpt->p << endl; << *(cpt->p) << endl; << cpt->*cpt->p ERROR!! << cpt->c << endl; " << *(cpt->c) << endl;

c.p == " c.x == " c.x == " c.c == " c.c[1]==

Salida: S.1 S.2 S.3 S.4 S.5 S.6 S.7 c.x == 13 c.x == 13 c.c == AEIOU c.p == 0041A220 c.x == 13 c.c == AEIOU 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 travs del constructor (invocado por el operador new). Las tres primeras salidas no tienen ninguna consideracin especial que no hayamos sealado antes. Solo recordar que en realidad no utilizan el objeto c (ni su puntero cpt), de forma que estas sentencias podran estar en cualquier parte siempre que las definiciones de xptr, pptr y cptrfuesen 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 haba sido posible anteriormente (como ejemplo, ver la salida 1.2 del ejemplo-2 en la pgina 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 indireccin 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 anloga a S.4 en el sentido que proporciona el valor del puntero (que es una direccin de memoria). Aunque en este caso, tambin por una larga tradicin C, y por tratarse de un puntero-a-cadena de caracteres, el compilador proporciona la cadena en vez del valor hexadecimal. La expresin *(cpt->c) de la salida S.7 obtiene el valor sealado por el puntero. En este caso el primer elemento de la cadena. Tema relacionado: Punteros a miembros de clases implcitas ( 4.12.2).

4.2.2 Aritmtica de punteros


1 Sinopsis La aritmtica de punteros se limita a suma, resta, comparacin y asignacin. Las operaciones aritmticas en los punteros de tipoX (punteros-a-tipoX) tienen automticamente en cuenta el

tamao real de tipoX. Es decir, el nmero de bytes necesario para almacenar un objeto tipoX [2]. Por ejemplo, suponiendo una matriz de double con 100 elementos, si ptr es un puntero a dicha matriz, la sentencia ptr++; supone incrementar el Rvalue de ptr en 6.400 bits, porque el tamao de la matriz es precisamente 100x64 bits. Nota: no confundir el puntero-a-matriz con un puntero a su primer elemento (que aqu sera puntero-a-double). La aritmtica realizada internamente en los punteros depende del modelo de memoria en uso y de la presencia de cualquier modificador superpuesto. Las operaciones que implican dos punteros exigen que sean del mismo tipo o se realice previamente un modelado apropiado .

2 Operaciones permitidas Sean ptr1, ptr2 punteros a objetos del mismo tipo, y n un tipo entero o una enumeracin; las operaciones permitidas y los resultados obtenidos con ellas son:

Operacin
pt1++ pt1-pt1 + n pt1 - n pt1 - pt2 pt1 == NULL pt1 != NULL pt1 <R> pt2 pt1 = pt2

Resultado puntero puntero puntero puntero entero booleano

Comentario Desplazamiento ascendente de 1 elemento Desplazamiento descendente de 1 elemento Desplazamiento ascendente n elementos [4] Desplazamiento descendente n elementos [4] Distancia entre elementos Siempre se puede comprobar la igualdad o desigualdad con NULL ( 3.2.1b)

booleano

booleano puntero puntero genrico

<R> es una expresin relacional (

4.9.12)

Asignacin Asignacin

pt1 = void

La comparacin de punteros solo tiene sentido entre punteros a elementos de la misma matriz; en estas condiciones los operadores relacionales ( 4.9.12): ==, !=, <, >, <=, >=, funcionan correctamente.

3 Homos sealado que cuando se realizan operaciones aritmticas con punteros, se tiene en cuenta el tamao de los objetos apuntados, de modo que si un puntero es declarado apuntando-atipoX, aadirle un entero n (al puntero) supone hacerlo hace avanzar un nmero n de objetos tipoX. Si tipoX tiene un tamao de 10 bytes, aadir 5 al puntero-a-tipoX lo hace avanzar 50 bytes en memoria (si se trata de punteros a elementos de una matriz, supone avanzar n elementos en la matriz [3]). Del mismo modo, la diferencia entre dos punteros resulta ser el nmero de objetos tipoX que separa a dos punteros-a-tipoX. Por ejemplo, siptr1 apunta al tercer elemento de una matriz, y ptr2 apunta al dcimo elemento, el resultado ptr2-ptr1 es 7 (en realidad, la diferencia de dos punteros solo tiene sentido cuando ambos apuntan a la misma matriz). observe que no est definida la suma entre punteros. Si ptr es un puntero a un elemento de una matriz, desde luego no existe un elemento tal como: "uno despus del ltimo", pero se permite que ptrtenga dicho valor. Si ptr1 apunta al ltimo elemento del array, ptr+1 es legal, pero ptr+2 es indefinido (lo que a efectos prcticos significa que devolver basura, o un runtime, volcado de memoria, etc). Si ptr apunta a uno despus del ltimo, ptr-1 es legal (puntero al ltimo elemento). Sin embargo, aplicando el operador de indireccin * a un puntero despus del ltimo conduce a una indefinicin. Informalmente puede pensarse en ptr + n como avanzar el puntero en (n * sizeof(tipoX)) bytes, siempre que ptr se mantenga en su rango legal (entre el primer elemento y uno despus del ltimo). La resta de dos punteros a elementos de la misma matriz, ptr1-ptr2, produce un entero n del tipo ptrdiff_t definido en <stddef.h> Este nmero representa la diferencia entre los subndices i y j de los dos elementos referenciados (n = i-j). Para esto es necesario que ptr1 y ptr2 apunten a elementos existentes, o uno despus del ltimo.

4 Ejemplos: N. 1. Expresin Resultado ip+10; Produce otro puntero; Si ip es un puntero al elemento m[j] de una matriz (dicho de otro modo: si *ip == &m[j] ), el nuevo puntero apunta a otro elemento m[j+10] de la misma matriz, con lo que *(ip+10) == &m[j+10]

2.

y = *ip+10; *ip += 1; ++*ip; ++ip;

Aade 10 al objeto *ip (objeto referenciado por ip) y lo asigna a y Equivale a: *ip = *ip + 1. Incrementa en 1 el valor (Rvalue) del objeto referenciado por ip. Igual que el anterior: incrementa en 1 el valor del objeto referenciado por ip El resultado es otro puntero. Equivale a ip = ip+1, es decir, incrementa en 1 el valor del puntero, con lo que el nuevo puntero seala a otra posicin (ver caso 1.) Igual que el caso anterior. Incrementa en 1 el valor del puntero. Igual que el caso 4. Dado que el parntesis tiene mxima precedencia y asocia de izquierda a derecha ( 4.9.0a), incrementa en 1 el valor del objeto referenciado por ip. Observe que el parntesis es necesario; sin l la expresin modifica la posicin de memoria sealado por ip. Es decir, se realiza ip++;. Si ip es un puntero al elemento m[j] de una matriz, el resultado es un puntero al elemento m[j+1]. Despus se tomara la indireccin, es decir, el resultado final sera el valor del elemento m[j+1]. Por tanto, la expresin *ip++ equivale a *(ip++)

3.

4. 5.

6. 7.

ip++; (*ip)++

8.

*ip+1;

El valor resultante es el de incrementar en 1 el valor del objeto apuntado por ip

5 Ejemplos Consideremos copiar una cadena NTBS ( 3.2.3f) definida por un puntero a su origen s, en un destino definido por un puntero p. Considerando que el final de la cadena est sealado por el carcter nulo, el proceso podra ser: while (*s != 0) { *p = *s; // copiar carcter s++ ; p++ ; } Teniendo en cuenta que estamos usando el postincremento ( 4.9.1) para s y p, y que cualquier <expresin> es cierta si <expresin> != 0. Lo anterior sera equivalente a: while (*s ) { *p = *s ; s++; p++; }

El incremento se puede expresar en la misma sentencia de asignacin: while (*s ) { *p++ = *s++ ; } La razn es que *p++ indica el carcter apuntado por p antes que sea incrementado. El postincremento ++ no cambia p hasta que el carcter ha sido actualizado (primero se efecta la asignacin y despus se incrementa). El orden de ejecucin de las operaciones involucradas es: 1.- Se asigna *p *s 2.- Se incrementa s 3.- Se incrementa p La comparacin puede hacerse en el mismo momento que la copia de caracteres: while ( *p++ = *s++) ; // 5.1

La razn es que despus de la ltima asignacin que correspondera al carcter ' \0' de s, el resultado de la asignacin sera justamente este valor (0 == falso) y se saldra del while. Por la razn inversa, la expresin *++p indica el carcter apuntado por p despus que ha sido incrementado. Ambos tipos de expresiones de asignacin con punteros, simultaneadas con pre/post incrementos/decrementos, son muy frecuentes en los bucles que involucran punteros. De hecho, las expresiones: *p++ = val; val = *--p; // cargar la pila con val // sacar de la pila el ltimo valor, asignarlo a val

son las sentencias estndar de cargar/descargar valores val de una pila LIFO [1] manejada por un puntero p. Nota: esta capacidad de C++ para permitir formas de cdigo tan extraordinariamente compactas, tiene fervientes defensores y acrrimos detractores. En mi opinin, el nico problema es que sin un entrenamiento previo, la primera vez que se topa uno con una expresin como la 5.1 anterior, puede quedarse bastante perplejo. Aunque en realidad solo se trate de uno ms de los "idioms" ( 4.13) de fondo de armario de cualquier programador C++

6 Conversin de punteros Un puntero de un tipo (tipoX) puede ser convertido a otro (tipoY) usando el mecanismo de conversin o modelado de tipos, que utiliza el operador (tipoY*). Ejemplo: char *str; int *ip; str = (char *)ip; // str puntero a char // ip puntero a int // asignacin str = ip (ip puntero a char)

De forma general, el operador-moldeador (tipoX *) puede convertir el operando (un puntero de cualquier tipo) a un puntero-a-tipoX. Como ejemplo de lo insidioso que pueden llegar a ser algunos rincones de los compiladores C++, considere el siguiente ejemplo: long strRchr (const String& str, char needle) { unsigned long stLen = str.len(); for (register unsigned long i = stLen - 1; i>=0; --i) { if (*(str.cptr + i) == needle) return i; } return -1L; } Se trata de una funcin que proporciona la posicin de la ltima ocurrencia de un carcter (needle) en un una cadena de caracteres (str). En nuestro caso los objetos de la clase String albergan cadenas alfanumricas. En concreto, el mtodolen() proporciona la longitud de la cadena, y el miembro cptr es un puntero al primer carcter. La funcin devuelve -1 si el carcter no se encuentre en la cadena. En caso contrario devuelve la posicin, empezando a contar desde cero para el primer carcter. Aunque compila sin dificultad, la rutina anterior, produce un extrao resultado negativo en las pruebas realizadas con el compilador BC++ 5.5, mientras que con GNU G++ 3.4.2-20040916-1 para Windows, produce un error fatal de runtime. Despus de perder un par de horas intentando diagnosticar el problema, y haber sopesado todas las posibilidades (incluyendo que el compilador estuviese "poseido" :-), result que estaba en el resultado de str.cptr + i (suma del puntero con el incremento). Cuando i es un unsigned long, la suma con el puntero produce un resultado negativo!!. Ningn otro tipo para i, produca error. Por ejemplo int o long. Observe que este comportamiento anmalo se produce a pesar de lo sealado al respecto por el Estndar [4], y de que losunsigned long son tipos enteros.

Temas relacionados: "Modelado de tipos con punteros" ( 4.2.1b) "Modelado de tipos" ( 4.9.9), en especial los operadores dynamic_cast y reinterpret_cast para convertir un puntero al tipo deseado.

4.2.3 Referencias
Nota: aunque las hemos incluido bajo el rubro general 4.2 dedicado a los punteros, desde el punto de vista de la tipologa, las referencias tienen muy poco o nada que ver con aquellos; unos y otros son tipos distintos. Sin embargo, existen similitudes en cuanto a su funcionalidad y a un cierto maridaje entre ambos tipos, en especial en el paso de parmetros a funciones.

1 Sinopsis Las referencias son un tipo de dato C++ estrechamente relacionado con los punteros. Una referencia de un objeto no es un objeto [5], en el sentido que no tiene su propio espacio de almacenamiento como ocurre con los punteros, y en consecuencia no pueden realizarse con ellas

muchas de las operaciones que se relacionan con objetos. Por ejemplo, obtener su direccin, crearlas con el operador new, o crear matrices de referencias. Una referencia es una especie de alias o "alter ego" del objeto. Como se ver a continuacin , este concepto, que tambin existe en otros lenguajes [7], es un recurso de C++ para pasar argumentos a funciones permitiendo que los argumentos no sean simples variables locales de la funcin, sino objetos del mbito que realiza la invocacin, lo que permite que la funcin pueda modificar objetos externos a ella. Nota: en este captulo nos referimos a las denominadas referencias tradicionales, o de Lvalue ("lvalue references") para distinguirlas de otro tipo, las referencias de Rvalue ("rvalue references") que son una adicin que ser incluida en la prxima revisin del Estndar C++, cuyo smbolo es && [8]. Nota: hemos ledo en algn libro [2] que "las referencias se incluyeron en C++ porque al Sr. Stroustrup pens que hacan ms simple la programacin (los programadores noveles podran evitar los punteros)". En realidad, la justificacin de este nuevo tipo es ms profunda y sutil. El paso de "referencias" a funciones esta suficientemente resuelto en C y C++ con los punteros, y desde luego es mi opinin que el Sr. Stroustrup no estaba pensando precisamente en los programadores noveles cuando dise su lenguaje. l mismo nos indica: "El uso principal de las referencias es para especificar argumentos (parmetros) y valores devueltos de funciones en general y para operadores sobrecargados en particular" [3]. Al tratar de la sobrecarga de operadores ( 4.9.18c) veremos una explicacin del verdadero sentido de esta afirmacin. La realidad es que las referencias fueron introducidas como un artificio imprescindible en el mecanismo de sobrecarga de operadores.

2 Sintaxis: La declaracin de una variable de este tipo se realiza mediante el declarador de referencia &. La sintaxis general es: <tipo_objeto> & <etiqueta_referencia> [ = <iniciador> ] Ejemplo: int x; ... int & z = x; 'referencia'

// decimos que x es el 'iniciador' y que z es la

Estas sentencias declaran e inicia la variable z como referencia-a-entero, y la asocia con la variable x (que es un entero). En adelante z acta como un alias de x, de forma que cualquier operacin sobre z equivale a hacerla sobre x. En realidad puede considerarse que z es "casi" un sinnimo de x (como si fuesen la misma variable). Por ejemplo, si hacemos: int x = 4; int & z = x; z = z * 4;

// z es referencia-a-int; z == 4 // z == 16, x == 16

2a

En el prrafo anterior hemos dicho "casi" porque la referencia y el objeto referenciado son de tipo

distinto. Por ejemplo, si nos referimos a las expresiones anteriores, x y z son de tipos distintos; x es tipo int, z es referencia-a-int (int&).

2.1 Observe que las expresiones: tipoX& var, tipoX &var y tipoX & var son todas equivalentes. En consecuencia, las tres expresiones que siguen tambin lo son [1]: int &z = x; int& z = x; int & z = x;

Cualquiera de estas expresiones crea el Lvalue z como un alias para la variable x (suponiendo que el iniciador es del mismo tipo que la referencia). Cualquier operacin en z tiene exactamente el mismo efecto que la operacin en x. Por ejemplo: z = 2 asigna 2 a x, y &z devuelve la direccin de x. Esto significa que siguiendo la expresines 2a podemos expresar: int* iptr = &z; // Ok. puntero a x

3 Observaciones En muchas situaciones prcticas puede ser indiferente utilizar una referencia o un puntero, sin embargo mantienen importantes diferencias que conviene conocer y que resumimos en el cuadro adjunto antes de comentarlas ms detenidamente. En cualquier caso, la mejor regla es recordar que las referencias pueden considerarse como un sustituto del identificador de un objeto, mientras que los punteros deben ser considerados siempre como objetos en s mismos [6]. Adems sus aritmticas son distintas.

Posibilidad de: Declaracin independiente de la definicin Asignarles un nuevo valor Referirse a un objeto de su mismo tipo Asignarles el valor void

Referencias Punteros No No No No Si Si Si Si

4 Declaracin Las referencias no pueden ser declaradas aisladas, de forma que tienen que estar indefectiblemente unidas a un objeto en su propia definicin (deben ser inicializadas en la declaracin). Adems, una vez declaradas no pueden ser reasignadas a otro objeto (como los punteros), por lo que resultan unidas de por vida al objeto inicial [4]. Por ejemplo: int& z; int& z = x // Error. // Ok.

int& z = y;

// Error.

Por la razn anterior, puesto que tienen que estar unidas a un objeto, no pueden referenciar a void: int& z = void; // Error.

S pueden ser inicializadas a otra referencia del mismo tipo, en cuyo caso sealan al objeto inicial. Ejemplo: int& max (int& a, int& b) { return (a >= b)? a : b; } ... int x = 10, y = 30; int& r1 = x; // Ok. r1 referencia a x int& r2 = r1; // Ok. r2 referencia a x int& r3 = max(x, y); // Ok. r3 referencia a y Tambin se pueden definir referencias a matrices: int m[5]; int (&rm)[5] = m; rm[1] = R3;

// Ok. rm referencia a m // Ok. m[1] == 30

Segn hemos visto hasta ahora, podemos imaginar que las referencias son una especie de punteros constantes, pero que no aceptan el lgebra de punteros ni pueden ser manipuladas igual. Existe un caso especial en la declaracin de referencias: cuando estas son miembros de clase. Acabamos de indicar que deben ser inicializadas en la declaracin, pero de otro lado, la sintaxis C++ no permite asignaciones en el cuerpo de la clase. La discrepancia se ha resuelto con un mecanismo especial de inicializacin que solo es vlido en estos casos ( 4.11.2d3). Referencias a objetos de clases implcitas ( 4.12.2).

5 Reasignacin de referencias: Despus de la definicin inicial, las sucesivas asignaciones a las referencias deben ser con objetos del mismo tipo. Por ejemplo, a una referencia-a-int solo se le pueden asignar tipos int. Pero estas asignaciones son en realidad al objeto inicialmente referenciado. Ejemplo: int x = 10, y = 20; int& refi = x; cout << "X = " << refi; refi = y; cout << "X = " << refi; cout << "X = " << x;

// // // // //

definicin inicial -> X = 10 (valor de x) Ojo!! Equivale a: x = y -> X = 20 (x es ahora 20) -> X = 20 (comprobacin)

6 Modelado:

En ocasiones el compilador realiza automticamente determinadas promociones de tipo ( 4.9.9) para hacer posible la asignacin. Por ejemplo, siguiendo con las definiciones anteriores (5 ): enum COLOR { ROJO, VERDE, AZUL}; COLOR c1 = VERDE; refi = c1; // L.3: Ok!! (ahora x = 1) cout << "X = " << refi; // -> X = 1 float f = 12.5; refi = f; cout << "F = " << f; cout << "X = " << refi; // L.6: Ok!! // -> F = 12.5 // -> X = 12 (Atencin!!)

En la asignacin L.3, la variable enumerada c1 es promovida a entero (cosa perfectamente factible, 4.7), el resultado es asignado a refi, lo que a la postre equivale a asignarlo a x. Un caso parecido es el de la asignacin L.6, donde el float f es promovido a entero (lo que implica una prdida de precisin) y el resultado asignado nuevamente a refi, la diferencia resultante entre f y x se muestra en las dos ltimas salidas.

Hay que hacer notar que la sustitucin del modelado implcito por uno explcito no altera para nada el resultado de este tipo de conversiones. Aunque el nuevo Estndar recomienda realizar todos los modelados de forma explcita. Entre otras razones, para facilitar la bsqueda y depuracin de posibles errores. Versin con modelado explcito ( 4.9.9b) de las sentencias anteriores:

int x = 10, y = 20; int& refi = x; cout << "X = " << refi; // -> X = 10 refi = y; // Ok!! (casting inecesario) cout << "X = " << refi; // -> X = 20 enum COLOR { ROJO, VERDE, AZUL}; COLOR c1 = VERDE; refi = static_cast<COLOR> (c1); // modelado esttico: Ok. cout << "X = " << refi; // -> X = 1 float f = 12.5; refi = static_cast<float> (f); // modelado esttico: Ok cout << "F = " << f; // -> F = 12.5 cout << "X = " << refi; // -> X = 12 (Atencin!!)

En ocasiones los tipos del Rvalue y Lvalue de la asignacin son tan dispares que el modelado no es posible (ni implcito ni explcito), por lo que el compilador muestra un mensaje de error al intentar la asignacin: struct C {float x; float y; } c1; refi = c1; // Error!! refi = static_cast<C> (c1); // Error !! en ninguno de estos dos casos es posible el modelado, ya que en ninguna circunstancia una estructura puede ser promovida a un entero, por lo que el compilador avisa con un error: Cannot convert 'C' to 'int' in function...

7 Punteros y referencias a referencias No es posible declarar punteros-a-referencias ni referencias-a-referencias: int x = 10; int& rti = x; int&* ptrti = &rti int&& rar = rti

// Ok referencia-a-x // Error!! Puntero-a-referencia // Error!! Referencia-a-referencia

Puesto que las referencias-a-tipoX son en realidad un "alter ego" del tipo referenciado, s es posible utilizar referencias para la definicin de otras referencias al mismo tipo: int& rti2 = rti; // Ok otra referencia-a-x

En contra de lo que ocurre con sus parientes cercanos los punteros, no es posible iniciar referencias con el operador new.

8 Referencias a punteros Aunque de poca importancia prctica, la referencia-a-puntero, es un alias que puede ser utilizado a todos los efectos como si fuese el propio puntero. Ejemplo: #include <iostream.h> int main() { int x = 10; int* ptr = &x; int*& ref = ptr; cout << "ptr-a-X cout << "ptr-a-X cout << "Valor X cout << "Valor X } Salida: ptr-a-X ptr-a-X Valor X Valor X = = = = 0065FE00 0065FE00 10 10 // ====== // puntero-a-int // referencia-a-puntero-a-int ref << endl; ptr << endl; *ref << endl; // M.6 *ptr << endl;

= = = =

" " " "

<< << << <<

En este ejemplo es digna de mencin la forma de declaracin de ref, referencia-a-puntero; y como a todos los efectos, incluso para aplicarle el operador de indireccin * ( 4.9.11), la referencia se comporta en M.6 como un alias perfecto del puntero, sealando al mismo objeto que aquel.

9 Referencias a funciones C++ permite definir referencias a funciones, aunque carecen de importancia prctica. La sintaxis para su declaracin es la misma que con los punteros, aunque como es usual, su inicializacin

debe hacerse en el punto de la declaracin. As mismo, pueden invocarse funciones a travs de sus referencias como si se tratara de punteros. Ejemplo: float sum(int i, int j) { float s = i + j; cout << "La suma es: " << s << endl; return s; } ... int main() { // =============== float (*fptr)(int, int) = sum; // puntero-a-funcin (iniciado) float (&fref)(int, int) = sum; // referencia-a-funcin int x = 2, y = 5; sum(x=2, y); // invocacin estndar fptr(x*2, y); // invocacin mediante puntero fref(x*2, y); // Ok. invocacin mediante referencia int& rx = x; // nuevo alias de x int& ry = y; // nuevo alias de y fref(rx*2, ry); // Ok. invocacin mediante referencia } Observe que los argumentos pasan "por valor" en todas las invocaciones (incluyendo la ltima).

10 Si en la declaracin de una referencia, el iniciador es una constante o un objeto de tipo diferente que el referenciado, entonces se crea un objeto temporal para el que la referencia acta como un alias. Considere los siguientes ejemplos: int& z = 6; Se crea un objeto temporal tipo int que recibe el valor 6; se crea tambin una referencia-a-int de nemnico z, a ese objeto temporal. El compilador avisa de esta circunstancia con una advertencia: Temporary used to initialize 'z' in function.... Observe que el objeto temporal solo es accesible a travs de su referencia (no tiene nemonico ni puntero especficos). float f = 12.1; int& z = f; Aqu se crea un objeto temporal tipo float de nemnico f y Rvalue 12,1. En la segunda sentencia se crea un objeto temporal tipo int que recibe el valor de f (que es promovido a int antes de la asignacin). Se crea una referencia-a-int que seala a dicho objeto temporal. El compilador avisa de esta circunstancia con el mismo mensaje que en el caso anterior: Temporary used to initialize 'z' in function.... Como consecuencia final, z contiene una imagen de f que es como su "versin int". Completemos el ejemplo anterior con una asignacin: float f = 12.1; int& z = f; z = 10;

El proceso es anlogo al anterior. En la tercera sentencia se asigna un valor 10 al objeto temporal, mientras que f sigue conservando su valor inicial. Como comprobacin, escribimos lo anterior en forma de programa ejecutable: #include <iostream.h> int main() { float f = 12.1; cout << "Valor f: int& z = f; cout << "Valor z: z = 10; cout << "Valor f: cout << "Valor z: } Salida: Valor Valor Valor Valor f: z: f: z: 12.1 12 12.1 10

" << f << endl; " << z << endl; " << f << endl; " << z << endl;

11 Consideremos tambin una variacin del ejemplo relativo al valor devuelto por una funcin ( 4.4.7), modificando el tipo aceptado en el segundo parmetro de la funcin max y aadiendo un "casting" para que en cualquier caso, el valor devuelto sea adecuado a la declaracin. #include <iostream.h> int& max (int& a, long& b) {if (a >= b) return a; return int(b); } int main () { // =========== int x =12, y = 22; cout << "Mximo: " << max(x, y) << endl; // M.2: cout << "Valor inicial: " << y << endl; int& mx = max(x, y); // M.4: Ok asignacin del mismo tipo mx = 30; // M.5: cout << "Valor final: " << y << endl; } Salida: Mximo: 22 Valor inicial: 22 Valor final: 22 En cada invocacin a la funcin max (M.2 y M.4), el compilador nos seala idntico mensaje de aviso: Temporary used for parameter 'b' in call to 'max(int &,long &)' in function main(). A su vez, la salida nos muestra que el valor y permanece inalterado. La razn, como se ha sealado antes, es que en cada invocacin en el mbito de main, se crea un objeto temporal de tipo referencia-a-long, que recibe el valor 22L, que es pasado a la funcin como segundo argumento (b). Este objeto temporal es promovido a int antes de su devolucin por el return. En consecuencia, se devuelve una referencia a un objeto temporal, objeto que es

modificado despus en M.5. Como queda de manifiesto en las salidas, la variable y permanece inalterada durante todo el proceso .

12 Argumentos por referencia: Aunque hemos insistido en que la razn ltima de la introduccin de referencias en C++ es posibilitar la sobrecarga de operadores, en la prctica su uso ms frecuente es el paso de argumentos a funciones "por referencia"; en especial cuando se trata de objetos definidos por el usuario (instancias de clases). Observe que mientras en C clsico solo se pasan argumentos por valor, en C++ es posible pasar argumentos por valor y por referencia. Ejemplo: void func1 (int); void func2 (int&); ... int sum = 3; func1(sum); func2(sum); // declara argumento de tipo int // declara argumento de tipo referencia-a-int // sum pasa por valor // sum pasa por referencia

Observe la utilizacin del argumento es idntica en la invocacin de ambas funciones, como si fuese "por valor". La diferencia en uno y otro caso estriba solo en la forma de declarar los argumentos en la definicin de la funcin. El argumento sum pasado por referencia en func2, puede ser modificado directamente desde dentro de esta funcin. Por contra, en func1 el argumento pasa por valor; la funcin recibe una copia de la variable sum, por lo que no puede modificar el valor original (la variable sum existente fuera de la funcin func1). Ver al respecto: Argumentos por valor/por referencia ( 4.4.5). Tenga en cuenta que en ocasiones, especialmente cuando los argumentos son objetos (instancias de clases), el verdadero motivo de pasar objetos "por referencia" no es precisamente para que la funcin pueda modificar el argumento (incluso se intenta evitar esto declarando el argumento como referencia constante), sino por razones de eficacia del cdigo . En realidad, ms que alguna nueva "funcionalidad", el paso de argumentos a funciones "por referencia" solo proporciona cierta comodidad adicional a la funcionalidad proporcionada por los punteros. Intentaremos aclararlo con un ejemplo muy sencillo:

#include <iostream> using namespace std;

// distintas formas de paso de argumentos

int func(int v, int& r, int* p) { // L4: cout << "Valores recibidos:" << endl; cout << "S4 v == " << v << endl; cout << "S5 r == " << r << endl; cout << "S6 p == " << p << endl; ++v; ++r; ++*p; // L9: cout << "Valores modificados:" << endl; cout << "S7 v == " << v << endl;

cout << "S8 r == " << r << endl; cout << "S9 p == " << p << endl; return v; } int main(void) { // ========= int x = 10; int y = 12; int z = 14; // M1: int* pz = &z; cout << "Valores iniciales:" << endl; // M3: cout << "S1 x == " << x << endl; cout << "S2 y == " << y << endl; cout << "S3 z == " << z << endl; int r = func(x, y, pz); cout << "Valores cout << "Sa x == cout << "Sb y == cout << "Sc z == return 0; } Salida: Valores S1 x == S2 y == S3 z == Valores S4 v == S5 r == S6 p == Valores S7 v == S8 r == S9 p == Valores Sa x == Sb y == Sc z == iniciales: 10 12 14 recibidos: 10 12 0065FDFC modificados: 11 13 0065FDFC finales: 10 13 15 finales:" " << x << " << y << " << z << << endl; endl; endl; endl; // M8: // M10:

Comentario: En L4 definimos una funcin que recibe tres argumentos: un entero (int); una referencia-aint (int&), y un puntero-a-int (int*). Esta funcin se limita a mostrar los valores recibidos. A continuacin altera estos valores (locales a la funcin), y finalmente muestra los valores modificados. Observe que el argumento p es un puntero. Por consiguiente, una alteracin del tipo ++p en L9, modificara su valor en el sentido de que sealara a una posicin distinta de la actual (la variable z de main). Esto podra tener resultados impredecibles, incluyendo un error fatal de runtime si la nueva direccin estuviese fuera del rea asignada al programa por el SO. Por esta razn se ha utilizado la forma *++p para modificarla. En realidad esta instruccin incrementa en una unidad el objeto sealado (la variable z de main).

El programa comienza en main, donde definimos tres enteros y un puntero-a-int; estos valores sern utilizados ms tarde como argumentos en la invocacin de la funcin. A continuacin el programa muestra los valores iniciales de estos objetos. Son las sentencias M3/M5 que proporcionan el primer grupo de salidas con los resultados esperados. En M8, se invoca la funcin con los valores definidos en M1 y M2. Aqu resultan pertinentes algunas observaciones: El primer parmetro (v) esperado por func es un entero. En consecuencia, se utiliza la variable x "tal cual" como primer argumento de la invocacin. Como segundo parmetro (r) la funcin espera una referencia-a-entero. Sin embargo no ha sido necesario construir previamente un objeto de este tipo. Como en el caso anterior, se pasa un entero (y) "tal cual" como segundo argumento. Aqu es el compilador el que se encarga de construir un objeto local en la funcin, de tipo referencia-a-entero, e iniciarlo con el valor de y. Es decir, para este argumento el compilador interpreta: int& r = y; A partir de ahora el objeto r (local a func) es un alias del objeto y de la funcin invocante (main). Es aqu donde est el truco y la magia del paso de argumentos "por referencia". El truco tiene en realidad dos partes: Primero: no es necesario copiar el objeto, solo se pasa una etiqueta (podramos decir que un puntero muy especial). En el ejemplo se ha pasado un int, pero cuando se pasan objetos por referencia suelen ser grandes, generalmente tipos abstractos (estructuras y clases) cuya copia puede resultar costosa en espacio de pila y en tiempo de carga en el marco correspondiente ( 4.4.6b), de modo que el paso por referencia supone un incremento notable en la eficiencia del programa (volveremos sobre esto a continuacin ). Segundo: el resultado es como si el mbito del objeto representado se "alargara" al interior de la funcin (podramos incluso utiliza el mismo nombre que en el exterior), lo que resulta en una gran comodidad sintctica. En L9 hemos incrementado y en una unidad travs de su "alias" r. Lo se comprueba en las salidas S8 y Sb. Observe que un incremento anlogo en el primer parmetro v pasado "por valor", no tiene absolutamente ninguna influencia sobre el argumento x utilizado por la funcin invocante (salidas S7 y Sa).

El tercer parmetro (p) esperado por la funcin es un puntero. Observe que aqu s ha sido necesario construir un objeto del tipo adecuado (pz) para utilizarlo como argumento en la invocacin. Un intento anlogo a los anteriores, utilizar el entero z "tal cual" como tercer argumento, habra originado la protesta del compilador: Error: Cannot convert 'int' to 'int *' in function main().... Observe que en la invocacin int r = func(x, y, pz); // M8:

el tercer argumento contiene el valor de la direccin de la variable z. El mismo resultado podra obtenerse utilizando: int r = func(x, y, &z); // M8b:

Ntese que aqu el smbolo & no es un declarador de referencia como en L4, sino el operador de referencia ( 4.9.11b) que obtiene la direccin de z, y es este valor el que se pasa como argumento (ver observacin al final ). En la salida Sc comprobamos cmo desde dentro de la funcin, tambin ha sido posible modificar el valor de un objeto externo (variable z), utilizando un puntero que ha pasado "por valor". Sin embargo, la sintaxis involucrada es algo ms complicada, dado que es necesario deferenciar el puntero.

13 Valores devueltos por referencia: Otro uso comn de este tipo de objetos es su utilizacin como valor de retorno de una funcin. Es el caso del valor devuelto por la funcin adjunta int& max (int& a, int& b) { if (a >= b) return a; return b; } Ms informacin al respecto en: Valor devuelto ( 4.4.7).

14 Criterios de eficiencia En ocasiones la utilizacin de referencias para el paso de argumentos a funciones o en el valor devuelto, est motivada solo por criterios de eficacia. En efecto, las secuencias de llamada y retorno de funciones ( 4.4.6b) implican la creacin de todas las variables locales de la funcin (incluyendo el valor que ser devuelto), as como la invocacin del constructor-copia para todos los argumentos que no han sido pasados por referencia. Cuando estos objetos son muy grandes. Por ejemplo, en instancias de clases con muchos datos o que derivan de jerarquas complejas, se requieren procesos de creacin y destruccin, a veces muy complicados, que pueden evitarse parcialmente utilizando referencias. En tales casos suele recurrirse a declarar constantes los argumentos pasados "por referencia" para evitar que la funcin pueda alterar su valor. Ver ejemplos al respecto: 4.9.18a; 4.9.18b2

Adems de ser utilizado como declarador de referencia, el smbolo & (mpersand) puede ser utilizado como operador AND entre bits ( 4.9.3) y como operador de referencia ( 4.9.11b). Ntese la diferencia entre las dos expresiones que siguen: int x; ... int* p = &x; // declara p puntero-a-entero, ahora contiene la direccin de x int& p = x; // declara p referencia-a-entero ahora es sinnimo de x

En la primera lnea se ha usado & como operador de referencia; en la segunda como declarador de referencia.

4.2.4 Puntero a funcin


1 Sinopsis Los punteros a funcin son uno de los recursos ms potentes y flexibles de C/C++, permitiendo tcnicas de programacin muy eficientes. Por ejemplo, escribir funciones que manejan diferentes tipos de datos; disear algoritmos muy compactos ("function dispatchers"), que pueden sustituir largas cadenas if...else o switch ( 4.10.2), o alterar el flujo de ejecucin del programa, modificando el orden de llamadas a funciones en base a determinadas prioridades ("adaptive program flow"). As mismo, resultan de gran ayuda en programas de simulacin y modelado. Sin embargo, los autores estn generalmente de acuerdo en que constituyen para el principiante uno de los puntos ms confusos del lenguaje. A esto se une que el tema suele ser despachado en una par de pginas como mucho. Por lo general, exponiendo el asunto con un ejemplo cuya lgica es de por s bastante difcil de seguir. Tmese como ejemplo el de Kernighan y Ritchie ( por lo dems, quizs sea el libro ms conciso y mejor escrito sobre C. K&R) que

En mi modesta opinin, parte de culpa de la confusin reside en la complejidad de la notacin utilizada para estos menesteres. En consecuencia, aconsejo al lector preste especial atencin a este aspecto y repase las Reglas de lectura ( Apndice 6.1).

2 La "direccin" de las funciones: Para hablar de punteros a funciones, previamente hay que establecer que las funciones tengan "direccin". Del mismo modo que en una matriz se asume que su direccin es la del primer elemento, se asume tambin que la direccin de una funcin es la del segmento de cdigo ("Code segment" 1.3.2) donde comienza el cdigo de dicha funcin [1]. Es decir, la direccin de memoria a que se transfiere el control cuando se la invoca (su punto de comienzo). Una vez establecido esto, no tiene que extraar que puedan definirse variables de un tipo especial para apuntar a estas direcciones. Tcnicamente un puntero-a-funcin es una variable que guarda la direccin de comienzo de la funcin. Pero como tendremos ocasin de comprobar, la mejor manera de pensar en ellos es considerarlos como una especie de "alias" de la funcin, aunque con una importante cualidad aadida: que pueden ser utilizados como argumentos de otras funciones. Por supuesto que los punteros a funcin se utilizan en ltimo extremo para acceder a la funcin sealada (las funciones existen para ser invocadas). Pero en general, cuando se utiliza este recurso, es muy frecuente que en alguno de los pasos intermedios se utilicen tales funciones como argumentos de otras. De hecho puede decirse que los punteros a funciones son en realidad un artificio de C++ para poder utilizar funciones como argumentos de otras funciones, dado que la gramtica de C++ no permite en principio utilizar funciones en la declaracin de parmetros ( 4.4.1). Por lo dems, no est permitida ninguna operacin de aritmtica (de punteros) con ellos [6]. Nota: en lo que respecta a las clases, pueden definirse punteros a funciones miembro ( 4.2.1g), pero generalmente tales punteros solo se utilizan para acceder a mtodos estticos ( 4.11.7), ya que para acceder a los miembros de clases en general no se utilizan

punteros, sino un operador especial [3], el operador de resolucin de mbito :: ( 4.9.19). Por otra parte, no es posible definir punteros a los constructores o destructores de clase, ya que son un tipo especial de funciones miembro de las que no puede obtenerse su direccin ( 4.11.2d).

3 Un puntero a funcin es una variable del tipo denominado: "puntero-a-funcin recibiendo A argumentos y devolviendo X", donde A son los argumentos que recibe la funcin y X es el tipo de objeto devuelto. Cada una de las infinitas combinaciones posibles da lugar a un tipo especfico de puntero-a-funcin. Considere detenidamente las declaraciones de los ejemplos siguientes (en todos ellos fptr es un puntero a funcin de tipo distinto de los dems). Observe una caracterstica que se repite: el nombre del puntero est siempre entre parntesis. Insistiremos en esta singularidad ms adelante ( 4.2.4a) al tratar de la declaracin de estos punteros. void (*fptr)(); fptr es un puntero a una funcin, sin parmetros, que devuelve void. fptr es un puntero a funcin que recibe un int como parmetro y devuelve void. fptr es puntero a funcin, que acepta un int y un char como argumentos y devuelve un int. fptr es puntero a funcin, que acepta sendos punteros a int y char como argumentos, y devuelve un puntero a int. fptr es puntero a funcin, sin parmetros que devuelve void y utiliza la convencin de llamada_USERENTRY [4] lpfnWndProc es puntero a funcin, sin parmetros que devuelve LONG y utiliza la convencin de llamada PASCAL [5].

void (*fptr)(int);

int (*fptr)(int, char);

int* (*fptr)(int*, char*);

void (_USERENTRY * fptr)(void);

LONG (PASCAL * lpfnWndProc)();

Cuando el valor devuelto por la funcin es a su vez un puntero (a funcin, o de cuaquier otro tipo), la notacin se complica un poco ms: int const * (*fptr)(); fptres un puntero a funcin que no recibe argumentos y devuelve un puntero a un int constante fptr es un puntero a funcin que recibe

float (*(*fptr)(char))(int);

un char como argumento y devuelve un puntero a funcin que recibe un int como argumento y devuelve un float. void * (*(*fptr)(int))[5]; fptr es un puntero a funcin que recibe un int como argumento y devuelve un puntero a un array de 5 punteros-a-void (genricos). fptr es un puntero a funcin que recibe dos argumentos (int y float), devolviendo un puntero a funcin que no recibe argumentos y devuelve un char. fptr es un puntero a funcin que no recibe argumentos y devuelve un puntero a un array de 5 punteros a funcin que no reciben ningn parmetro y devuelven long.

char (*(*fptr)(int, float))();

long (*(*(*fptr)())[5])();

4 Matrices de punteros a funcin Los punteros a funcin tambin pueden agruparse en matrices. Por ejemplo: int (* afptr[10])(int); // matriz de 10 punteros a funcin

De la propia definicin se desprende que en estas matrices, todos los punteros sealan funciones que devuelven el mismo tipo de valor y reciben los mismos tipos de parmetros. En el ejemplo anterior, la variable afptr es declarada como matriz de 10 punteros a funcin que reciben int y devuelven int. Ms sobre matrices de punteros en ( 4.3.5). Observe que la gramtica C++ acepta la existencia de matrices de punteros-a-funcin, que resultan muy tiles, pero no la existencia de matrices de funciones. Sin embargo, como veremos ms adelante, en la prctica son equivalentes; para casi todos los efectos una funcin puede sustituirse por su puntero. Estas matrices permiten invocar funciones (a travs de sus punteros) utilizando los miembros de la matriz mediante notacin de subndices, lo que da mucho juego en determinadas circunstancias. Por ejemplo, el caso anterior permitira invocaciones del tipo: int z = afptr[n](x);

4.2.4a Declaracin y definicin de punteros a funcin


1 Sintaxis Como se ha visto en los ejemplos ( funcin tiene la siguiente sintaxis: 4.2.4), el ncleo de todas las declaraciones de punteros a

<tipo_devuelto> ... (* nombre_puntero) (<parametros>) ...

<tipo_devuelto> es el tipo de vuelto por la funcin sealada por el puntero, podramos decir que es el tipo de variable sealada por el puntero en ltimo extremo [2]. <parametros> es la lista de los argumentos aceptados por la funcin sealada por el puntero.

Observe que el parntesis (* nombre_puntero) es fundamental. Esta parte es conocida como ncleo de la declaracin, a la derecha del ncleo se incluye la lista de parmetros (que puede estar vaca) y a su izquierda el valor devuelto por la funcin, que debe ser nico (puede servoid). Sin el ncleo la declaracin cambia drsticamente su sentido, ya que entonces no es considerada por el compilador como la declaracin de una variable de tipo puntero-a-funcin. Por ejemplo: int (*fptr)(int); // puntero-a-funcin que recibe un int y devuelve un int int *fptr (int); // funcin que recibe un int y devuelve puntero-a-int En consecuencia: void func(int (*fptr)(int)); void func(int *fptr(int)); // Ok declaracin correcta de una funcin // Error!! expresin ilegal en C/C++

Nota: a pesar de lo indicado en esta ltima sentencia, puede comprobarse que, al menos con los compiladores Borland C++ 5.5 y MS Visual C++ 6.0, una expresin como la anterior puede ser compilada sin que se presente ningn problema. En la pgina adjunta ( Nota-1) se ofrece una explicacin detallada del motivo, que no est en contradiccin con el hecho repetidamente enunciado de que C/C++ nopermiten utilizar una funcin como argumento de otra funcin [3]. Aunque la sintaxis utilizada resulta difcil de interpretar en algunos casos, la notacin de punteros a funcin sigue cierta regla que est relacionada con la sintaxis de la funcin a la que apuntan. Si consideramos que la definicin de una funcin tiene tres partes: valor devuelto; argumentos utilizados, y nombre. La representacin de un puntero a tal funcin se obtiene sustituyendo el nombre por la indicacin de un puntero y separndolo del resto por un parntesis.

Ejemplos: Declaracin de funcin char* func (char*); Funcin que acepta un puntero-a-char y devuelve un puntero-a-char char const * func (); Declaracin del puntero-a-funcin char* (*func) (char*); Puntero-a-funcin que acepta un puntero-achar y devuelve un puntero-a-char char const* (*func) ();

Funcin que no acepta argumento y devuelve un Puntero-a-funcin que no acepta argumentos y puntero-a-charconstante. devuelve un puntero-a-char constante

struct S func (char*); Funcin que acepta un puntero-a-char y devuelve una estructura tipoS. int* func (struct S* Sptr); Funcin que acepta un puntero-a-estructura tipo S y devuelve un puntero-a-int int* (C::* func())(char); Funcin que no recibe argumentos, devuelve un puntero a funcin miembro de la clase C que recibe un char y devuelve un puntero-a-int. int (*(*f())[10])();

struct S (*func) (char*); Puntero-a-funcin que acepta un puntero-achar y devuelve una estructura tipo S. int* (*func) (struct S* Sptr); Puntero-a-funcin que acepta un puntero-aestructura tipo S y devuelve un punter-a-int int* (C::* (*func)())(char); Puntero-a-funcin que no recibe argumentos, devuelve un puntero-a-funcin miembro de la clase C que recibe un char y devuelve un punter-a-int. int (*(*(*f)())[10])();

Funcin que no acepta argumentos, devuelve un Puntero-a-funcin que no acepta argumentos, puntero a una matriz de diez punteros-a-funcin devuelve un puntero a una matriz de diez que no aceptan argumentos y devuelven unint. punteros-a-funcin que no aceptan argumentos y devuelven un int.

1.1 Convencin de llamada Tcnicamente, la convencin de llamada ( 4.4.6a) no forma parte del "tipo" de la funcin, de forma que, por ejemplo, las funciones que siguen son del mismo tipo y el compilador muestra el correspondiente error si se intentan compilar en la misma unidad de compilacin: float __stdcall foo (float x) { return 2 * x; float foo (float x) { return 2 * x; } } // L.1 // L.2

En concreto, los errores obtenidos en ambas lneas con Borland C++ son los siguientes (otros compiladores dan errores similares): L.1: L.2: Error ...: Earlier declaration of '__stdcall foo(float)' Error ...: Type mismatch in redeclaration of '__stdcall foo(float)'

Sin embargo, en lo que se refiere a la definicin de punteros-a-funcin, s es necesario tener en cuenta este detalle en la declaracin, ya que los punteros a funciones con distintas convenciones de llamada no son equivalentes entre s. La convencin de llamada en la declaracin de punteros-a-funcin se utilizan dos sintaxis distintas, segn sean los compiladores MSVC++ y BC++ o GNU gcc. En los dos primeros, el especificador de llamada se sita entre e valor devuelto y el parntesis que contiene el nombre del puntero. En el compilador GNU esto se hace incluyendo la palabra __attribute__ seguida del nombre del especificador entre dobles parntesis al final de la declaracin. Por ejemplo, las declaraciones de punteros para las funciones anteriores seran:

float __stdcall (* fooPtr) (float); // para foo de L.1 MSVC++ y BC++ float (* fooPtr) (float) __atribute__((stdcall)); // para foo de L.1 GNU g++ float (* fooPtr) (float); // para foo de L.2 todos los compiladores estndar

2 Definicin: Como ocurre con los dems tipos de variables, antes de usar los punteros a funcin es necesario definirlos, es decir, asignarles un valor; en este caso la direccin de una funcin concreta. Esto puede hacerse en el mismo momento de la declaracin o despus. Nota: recuerde que el puntero-a-void no puede usarse para sealar a una funcin ( void* fptr = &func; // Error!! 4.2.1d)

Una observacin importante a tener en cuenta desde el punto de vista de la sintaxis, es que la direccin de una funcin func() puede indicarse con el operador de referencia & ( 4.9.11) &func [1], o solo con el nombre func. C++ presenta aqu la misma situacin sintctica que cuando en 4.3.2 decimos que el identificador de una matriz puede ser interpretado como la direccin de su primer elemento. Es decir: char func(int); char (*fptr)(int); fptr = &func; // declara una funcin // declara fptr puntero-a-funcin // inicia fptr

las dos ltimas sentencias son equivalentes a: char (*fptr)(int); fptr = func; tambin son equivalentes a: char (*fptr)(int) = func; // define ptr (declara e inicia)

// inicia fptr

En el siguiente epgrafe ( 4.2.4b) veremos que esta equivalencia puntero funcin (fptr == func) es utilizada tambin para la invocacin de la funcin a travs de su puntero [4]. Nota: la Librera Estndar C++, que contiene una amplia coleccin de tcnicas de programacin avanzadas, incluye mltiples ejemplos de punteros-a-funcin que utilizan la dualidad puntero funcin antes sealada. Por ejemplo, para extender el mecanismo de sobrecarga de operadores en las operaciones de E/S.

2.1 Por supuesto, en este tipo de asignaciones es imprescindible que la funcin asignada al puntero responda a la definicin de este. Es decir: tanto el tipo de dato devuelto como los

parmetros deben ser adecuados, -en cambio, el posible especificador de excepcin ( 1.6.4) que pudiera tener la funcin no se considera parte de su tipo y por consiguiente no afecta al tipo de puntero-. Por ejemplo: char func(int); int (*fptr)(int) = func; char(*fptr)(char) = func; char(*fptr)(int) = func;

// Error: desacuerdo en valor devuelto // Error: desacuerdo en parmetro // Ok: todo correcto

2.2 Si se trata de definir una matriz de punteros, tambin se pueden declarar e iniciar en la misma sentencia: void func1(char letra); // prototipo de func1 void func2(char letra); // prototipo de func2 void (* afptr[2])(char) = {func1, func2}; // definicin de afptr en la ltima lnea el tamao de la matriz est implcito en la inicializacin, de forma que tambin se podra haber puesto: void (* afptr[])(char) = {func1, func2};

Tambin puede iniciarse mediante subndices (en este caso no puede ahorrarse el tamao de la matriz en la declaracin): void func1(char letra); void func2(char letra); void (* afptr[2])(char); afptr[0] = func1; afptr[1] = func2;

// declaracion de afptr // inicia afptr[1] // inicia afptr[2]

Observe que los elementos de una matriz (de punteros o de cualquier otro tipo de objetos) son del mismo tipo. En el caso de una matriz de punteros, significa que estos deben apuntar a funciones recibiendo el mismo tipo de argumentos y devolviendo lo mismo, lo que significa a su vez que sus prototipos ( 4.4.1) deben ser idnticos. Las diferencias solo pueden estar en el nombre y en el cuerpo de las funciones referenciadas.

3 typedef y la declaracin de punteros a funcin Como hemos visto, la notacin utilizada por C++ en la declaracin de punteros a funcin puede llegar a ser bastante compleja, por lo que es muy comn la costumbre de utilizar typedef auxiliares, en especial cuando se trata de definiciones ( 3.2.1a). Ejemplo: #include <iostream> #include <cstdlib> using namespace std; typedef void (*PFV)(); // L.5

void fun1() { cout << "Primera" << endl; }

void fun2() { cout << "Segunda" << endl; } void fun3() { cout << "Tercera" << endl; } void fun4() { cout << "Argumento No valido" << endl; } int main( int argc, char * argv[] ) { PFV apf[] = { &fun1, &fun2, &fun3, &fun4 }; if (argc > 1 && *argv[1] > '0' && *argv[1] <= (*apf[atoi( argv[1] ) - 1])(); else (*apf[3])(); return 0; } // ================== // M.1 '4') // M.3 // M.5

Comentario: El ejemplo comprueba el argumento pasado con el programa ( 4.4.4). Este argumento puede ser un entero de 1 a 3, invocndose la funcin correspondiente al nmero utilizado. En los dems casos se invoca la funcin cuarta. En la segunda lnea se ha utilizado un typedef para definir que PFV es un tipo puntero a funcin que devuelve void y no recibe ningn argumento. A continuacin se definen cuatro funciones que corresponden con esta definicin. El programa comienza definiendo apf, un array de punteros a funciones que devuelven void y no reciben ningn argumento (precisamente utilizando el tipo PFV). En la misma lnea se inicializa con las direcciones de las cuatro funciones fun1 a fun4. Observe que la sentencia M.1 podra haberse sustituido por otra equivalente ms elegante: PFV apf[] = { fun1, fun2, fun3, fun4 }; // M.1bis

Las sentencias M.3 y M.5 son la forma cannica de invocacin de las funciones mediante sus punteros ( 4.2.4b)

Otra posibilidad sintctica es utilizar el typedef auxiliar para definir un tipo de funcin en s en lugar del puntero-a-funcin. En este caso, la definicin del puntero-a-funcin debe ser modificada convenientemente. En el ejemplo anterior, las sentencias L.5 y M.1 podran tener el siguiente aspecto: typedef void PFV(); // L.5 PFV* apf[] = { &fun1, &fun2, &fun3, &fun4 }; // M.1 Observe que en este caso, PFV define una funcin que no recibe argumentos y devuelve void, mientras que apf sigue siendo una matriz de 4 punteros a funciones que no reciben argumento y devuelven void.