Sei sulla pagina 1di 27

4.12.

1a Instanciacin explcita & versin explcita (sobrecarga de funciones


genricas)
1 Sinopsis
Hemos sealado que la instanciacin de la plantilla se realiza cuando el compilador encuentra una
invocacin de la funcin genrica o se obtiene su direccin (
4.12.1), y que solo puede existir
una versin de cada especializacin de la funcin genrica. Estas premisas conducen a que sea
posible evitar la generacin automtica para uno o varios tipos concretos, mediante dos
procedimientos:

a. Proporcionando una versin codificada de forma "manual" de la funcin (versin


explcita ).
b. Forzar una instanciacin especfica de la plantilla (instanciacin explcita), de forma
que se genera el cdigo de una especialidad concreta, con independencia de que
posteriormente se requiera o no, la utilizacin del cdigo generado. La instanciacin puede
realizarse de dos formas:
o

b1 Forzar la instanciacin de la plantilla "tal cual" para un tipo particular. Esta


instancia explcita tendra el comportamiento genrico definido en la plantilla, por lo
que la denominamos instanciacin explcita general
.
b2 Forzar una instanciacin para un tipo particular en las mismas condiciones que
el anterior (con independencia de la posible utilizacin del cdigo generado en el
programa), pero definiendo un nuevo comportamiento, distinto del general definido
en la plantilla. En otras palabras: instanciar una versin sobrecargada de la funcin
para un tipo especfico. La denominamos instanciacin explcita particular
.

Como veremos a continuacin, estas posibilidades tienen distinta utilidad y ligeras diferencias de
detalle. Aunque son tcnicas diferentes, el resultado final es anlogo: la existencia de una (o
varias) especializaciones concretas de la funcin, lo que nos obligar a contemplar una
generalizacin de la sobrecarga de funciones (
4.4.1a) que incluya funciones explcitas y
genricas.
Adems de las anteriores existe una cuarta posibilidad, que en realidad podra considerarse una
variacin del caso b1 anterior (la hemos visto en el captulo anterior bajo el epgrafe
"Especificacin explcita de parmetros" (
4.12.1). Tiene la particularidad de que la instanciacin
no se realiza independientemente de la utilizacin posterior del cdigo, sino en el momento de su
uso (cuando existe una invocacin de la funcin). A esta modalidad la denominamos instanciacin
implcita especfica, y resulta de utilidad cuando a la luz de los argumentos utilizados en la
invocacin, puede existir ambigedad sobre los parmetros que debe utilizar el compilador en la
plantilla. Su caracterstica principal es que no se permite al compilador decidir por su cuenta que
parmetros utilizar en la plantilla en funcin de los argumentos de la invocacin, sino que se le
ordena generar una plantilla utilizando unos parmetros determinados (de ah el nombre que
utilizamos para ella) y que aplique los argumentos actuales en la invocacin del cdigo resultante.
Con independencia de la explicacin ms detallada que sigue, para situarnos en el tema
adelantamos un esbozo de lo que significa la literatura anterior referida a un caso muy sencillo:
// funcin genrica (declaracin)
template<class T> T max(T, T);

...
// funcin genrica (definicin)
template<class T> T max(T a, T b) { return (a > b) ? a : b; }
...
// versin explcita
char max(char a, char b) { return (a >= b) ? a : b; }
...
// instanciacin explcita general
template T max<long>(long a, long b);
...
// instanciacin explcita particular
template<> T max<double>(double a, double b){ return (a >= b) ? a : b; };
...
// instanciacin implcita especfica
int x = max<int>(x, 'c');

1.1 Introduccin
Para introducirnos en el tema , considere un caso en el que utilizamos una funcin
genrica igual( ) para comprobar si dos objetos son iguales:
#include <iostream>
using namespace std;
class Vector {
public: float x, y;
bool operator==(const Vector& v) {
// L6
return ( x == v.x && y == v.y)? true : false;
}
};
template<class T> bool igual(T a, T b) {
// L10: funcin genrica
return (a == b) ? true : false;
};
void main() {
// =====================
Vector v1 = {2, 3}, v2 = {1, 5};

int x = 2, y = 3;
double d1 = 2.0, d2 = 2.2;
if (
else
if (
else
if (
else

igual(v1, v2) ) cout << "vectores iguales" << endl;


cout << "vectores distintos" << endl;
igual(d1, d2) ) cout << "doubles iguales" << endl; // M7
cout << "doubles distintos" << endl;
igual(x, y) ) cout << "enteros iguales" << endl;
cout << "enteros distintos" << endl;

}
Salida:
vectores distintos
doubles distintos
enteros distintos
Comentario
En L6 se ha definido una versin sobrecargada del operador de igualdad == para los miembros de
la clase. En L10 se define la funcin genricaigual(T, T).
Hasta aqu nada nuevo; el compilador ha generado y utilizado correctamente las especializaciones
de igual( ) para las invocaciones con tipos int,double, y Vector.

2 Versin explcita
Consideremos ahora que es necesario rebajar la exigencia para que dos variables sean
consideradas iguales en el caso de que sean doubles. Para ello introducimos una instancia
de igual codificada manualmente en el que reflejamos la nueva condicin de igualdad:
#include <iostream>
using namespace std;
class Vector {
public: float x, y;
bool operator==(const Vector& v) {
return ( x == v.x && y == v.y)? true : false;
}
};
template<class T> bool igual(T a, T b) {
// L10: funcin genrica
return (a == b) ? true : false;
};
bool igual(double a, double b) {

// L13: versin explcita

return (labs(a-b) < 1.0) ? true : false;


};

void main() {

// =====================

Vector v1 = {2, 3}, v2 = {1, 5};


int x = 2, y = 3;
double d1 = 2.0, d2 = 2.2;
if (
else
if (
else
if (
else

igual(v1, v2) ) cout << "vectores iguales" << endl;


cout << "vectores distintos" << endl;
igual(d1, d2) ) cout << "doubles iguales" << endl; // M7b
cout << "doubles distintos" << endl;
igual(x, y) ) cout << "enteros iguales" << endl;
cout << "enteros distintos" << endl;

}
Salida:
vectores distintos
doubles iguales
enteros distintos
Comentario
La versin explcita para tipos double de L13 utiliza la funcin de librera labs para conseguir
que dos doubles sean considerados iguales si la diferencia es solo en los decimales. La inclusin
de esta definicin supone que el compilador no necesita generar una versin de igual( ) cuando los
parmetros son tipo double. En este caso, el compilador utiliza la versin suministrada
"manualmente" por el programador.
Adems de permitir introducir modificaciones puntuales en el comportamiento general, las
versiones explcitas pueden utilizarse tambin para eliminar algunas de las limitaciones de las
funciones genricas. Por ejemplo, si sustituimos la sentencia M7 del primer caso (1.1
):
if ( igual(d1, d2) ) cout << "doubles iguales" << endl;

// M7

por:
if ( igual(d1, y) ) cout << "doubles iguales" << endl;

// M7b

Se obtienen un error de compilacin: Could not find a match for


'igual<T>(double,int)'. La razn es que, como hemos visto ( 4.12.1), el compilador no
realiza ningn tipo de conversin sobre el tipo de los argumentos utilizados en las funciones
genricas, y en este caso no existe una definicin de igual() que acepte un double y un int. En
cambio, la misma sustitucin de M7 cuando existe una versin explcita paraigual(double,
double), no produce ningn error. La razn es que para las funciones normales el compilador s
es capaz de realizar automticamente determinadas transformaciones de los argumentos actuales
para adecuarlos a los esperados por la funcin (
4.4.6).

3 Instanciacin explcita
El Estndar ha previsto un procedimiento para obligar al compilador a generar el cdigo de una
especializacin concreta a partir de la plantilla-funcin. Esta instanciacin forzada se
denomina instanciacin explcita, y utiliza el especificador template aislado (sin estar seguido
de <...> ).

Recuerde que la definicin de la plantilla igual es:


template<class T> bool igual(T a, T b) {...}

// funcin genrica

La sintaxis para generar una versin de igual especfica para doubles sera la siguiente:
template bool igual<double>(double a, double b);
explicita 3a

// instancia

Observe la sintaxis utilizada: la lista de parmetros <...> se ha cambiado de posicin respecto a la


declaracin de la plantilla.
La inclusin de una instanciacin explcita como la anterior (la llamaremos general porque sigue el
comportamiento general definido por la plantilla), origina la aparicin del cdigo correspondiente a
la especializacin solicitada aunque en el programa no exista una necesidad real (invocacin) de
dicho cdigo. Esta instancia explcita general desempea un papel anlogo al de una versin
que se hubiese codificado manualmente (versin explcita).
Observe que la versin instanciada en la expresin 3a es concordante con la plantilla, por lo que
no sirve para realizar modificaciones especficas como las realizadas en el ejemplo anterior en el
caso de los float. Sin embargo, tambin es posible especificar una definicin particular para la
especialidad que se instancia aadiendo el cuerpo adecuado. A esta versin la
denominamos instancia explcita particular. La sintaxis seria la siguiente:
template<> bool igual<double>(double a, double b) {
explicita particular
return (labs(a-b) < 1.0) ? true : false;
};

// instancia

Los ngulos <> despus de template indican al compilador que sigue una especializacin
particular de una plantilla definida previamente. Como puede figurarse el lector, el resultado es
similar al que se obtendra una versin explcita (2
).
Nota: al llegar a este punto el lector puede, con razn, suscitarse la cuestin Que diferencia
existe entonces entre una versin explcita y una instanciacin explcita particular?.

3.1 Es un error intentar la existencia de ms de una definicin para la misma funcin, ya sea esta
una instanciacin implcita; explcita, o una versin codificada manualmente. Por ejemplo:
bool igual(double& a, double& b) {
// versin explcita
return (labs(a-b) < 1.0) ? true : false;
};
template bool igual<double>(double& a, double& b); // instancia
explicita general
En las condiciones anteriores el compilador puede generar un error, una advertencia, o
sencillamente ignorar el segundo requerimiento, ya que previamente existe una versin explcita de
la funcin con idntica firma.
En cualquier caso, es una regla que el compilador dar
preferencia a una funcin normal (versin explcita) sobre cualquier forma de instanciacin,
explcita o implcita, al utilizar una funcin.

En la pgina adjunta se incluye un ejemplo ejecutable (


Ejemplo). Observe que la instanciacin
explcita no es realmente necesaria desde el punto de vista del programa (a no ser que se busque
un efecto de conversin automtica de tipos como el descrito). El Dr. Stroustrup aclara que este
mecanismo solo es de utilidad para la depuracin, optimizacin, y control de los procesos de
compilacin y enlazado.

4 Conversin de argumentos en versiones explcitas e implcitas


Recuerde que cuando se siguen los pasos de la congruencia estndar de argumentos para
resolver la sobrecarga de funciones (
4.4.1a), el compilador puede realizar determinadas
conversiones para realizar la invocacin.
4.1 Considere el siguiente ejemplo con una funcin explcita:
#include <iostream>
using namespace std;
int max(int a, int b) { return (a > b) ? a : b; }

// L4

void main() {
// =====================
int x = 2, y = 3;
char c = 'x';
cout << "Mayor: " << max(x, y) << endl;
// M3
cout << "Mayor: " << max(x, c) << endl;
// M4
}
Salida:
Mayor: 3
Mayor: 120
Comentario
La invocacin en M3 de la funcin max( ) no presenta problemas. Pero en M4, para conseguir el
ajuste con la mejor (y nica) versin de la funcin, el compilador debe realizar una promocin
de char a int. Lo que se realiza sin problema segn puede verse en la segunda salida, donde ha
sustituido el char 'x' por su valor ASCII (ver conversiones aritmticas
2.2.5).

4.2 Considere ahora una nueva versin, en la que las invocaciones a la versin explcita
de max (en M3 y M4) son sustituidas por una versin implcita, generada en cada caso por una
funcin genrica:
#include <iostream>
using namespace std;
template<class T> T max(T a, T b) { return (a > b) ? a : b; } // L4
void main() {
// =====================
int x = 2, y = 3;
char c = 'x';
cout << "Mayor: " << max(x, y) << endl;
// M3

cout << "Mayor: " << max(x, c) << endl;

// M4

En este caso se recibe un error de compilacin en M4: Template parameter 'T' is


ambiguous could be 'char' or 'int'. La razn es que cuando se trata de definiciones
generadas automticamente por el compilador (versiones implcitas), solo se realizan conversiones
triviales de argumentos
. As pues, no se realiza la conversin char a int o viceversa.
Para resolver la ambigedad pueden utilizarse dos formas:
a.- Proporcionarse una versin explcita. Por ejemplo, aadir la siguiente lnea al cdigo anterior
[2]:
int max(int a, char b) { return (a > b) ? a : b; }

// L5

b.- Incluir una cualificacin explcita en la invocacin. Por ejemplo, sustituir la sentencia M4 por:
cout << "Mayor: " << max<int>(x, c) << endl;

// M4bis

Corolario:
Estas consideraciones sobre los modos de conversin de argumentos, puede tener en la prctica
importantes consecuencias. Empezando por que, como hemos visto, el hecho de que una funcin
explcita se comporte bien en un programa
no significa que lo siga haciendo si se generaliza y
se transforma en una plantilla.
La cuestin no solo alcanza a las funciones genricas; como veremos a continuacin (
4.12.2),
las funciones-miembro de clases genricas son a su vez funciones genricas con los mismos
parmetros que la clase genrica a que pertenecen, con lo que el problema descrito puede
reproducirse tambin en las clases (volveremos sobre este particular al tratar el captulo
correspondiente).

4.12.1b Funciones genricas (continuacin)


1 Polimorfismo en funciones genricas
En captulos precedentes nos hemos referido a esta caracterstica del lenguaje C++ que concierne
tanto a funciones normales ( 4.4.1) como a mtodos de clases ( 4.11.2a2). Es posible tambin
la sobrecarga de funciones genricas; incluso mezclando definiciones de plantillas del mismo
nombre con funciones explcitas. Por ejemplo:
template <class T> int modulo(T);
template <class T> int modulo(Array<T>);
int modulo(Vector2d);
int modulo(Vector3d);
int modulo(Complejo);
Cuando ms tarde el compilador encuentra una invocacin a la funcin modulo( ), deduce la
versin a utilizar (o generar si corresponde a una plantilla), basndose en los argumentos

utilizados, y en una generalizacin de las reglas de congruencia estndar de argumentos utilizadas


en la sobrecarga de funciones ( 4.4.1a).

2 Las plantillas y la organizacin del cdigo


Para la organizacin de las plantillas en el cdigo pueden seguir tres estrategias distintas:
Fichero nico
Fichero combinado
Ficheros mltiples (compilacin separada)
2.1 El mtodo de fichero nico exige que el compilador encuentre la definicin de las plantillas
antes de su uso en cada unidad de compilacin. Presenta el inconveniente de que las plantillas
definidas en un mdulo no son visibles desde los dems.
Su uso est recomendado cuando se utilizan plantillas en programas pequeos, con un solo
fuente, o cuando existen varias unidades de compilacin ( 1.4.2) pero las plantillas se utilizan
solo en una de ellas, la organizacin del cdigo suele ser la misma que con funciones explcitas, es
decir: Primero se declaran, a continuacin se utilizan y finalmente se definen segn el siguiente
esquema:
template <class T> void f1(const T&); // Declaracin
...
f1(t1);
// Uso
...
template <class T> void f1(const T& ref) { // Definicin
...
}
Las otras dos estrategias se aconsejan para programas ms extensos, en los que existen varias
unidades de compilacin que referencian a las mismas plantillas

2.2 El mtodo de fichero combinado consiste en situar en un fichero de cabecera las


definiciones de todas las funciones genricas. Posteriormente este fichero es incluido en todos los
mdulos que utilicen la funcin. La situacin queda esquematizada como sigue:

// cabecera.h

// main.cpp
#include <cabecera.h>

#ifndef F_1

...

#define F_1

int main() {

template<T> void f1(T t) {


/* definicion... */
};

...
f1(a);
...

// =====

#endif

// F_1

// modulo1.cpp

// modulo2.cpp

#include <cabecera.h>

#include <cabecera.h>

...

...

{
...

...

f1(X);

f1(Z);

...

...

De esta forma se garantiza que la plantilla pueda ser utilizada por ms de una unidad de
compilacin, y se delega en el compilador la tarea de generar el cdigo adecuado y eliminar las
definiciones redundantes. Observe que es justamente la misma estrategia que con las funciones
explcitas definidas por el usuario ( 4.4.1). El inconveniente es que la declaracin est presente
en todos los mdulos, con la consiguiente sobrecarga para el compilador que debe procesarla.

2.3 El mtodo de compilacin separada consiste en incluir una declaracin en cada mdulo
antes de su uso, e incluir la definicin en un fichero que es compilado separadamente. En este
caso, la disposicin de ficheros queda como sigue:

// cabecera.h

// definiciones.cpp
#include <cabecera.h>

#ifndef F_1
#define F_1

export template<T> void f1(T t) {

template<T> void f1(T); //


declaracin

...

// definicin

}
...

// otras declaraciones
...

#endif

// otras definiciones

// F_1

// modulo1.cpp

// modulo2.cpp

#include <cabecera.h>

#include <cabecera.h>

...

...

{
...
f1(X);

...
// usar funcin

f1(Z);

...
}

// usar funcin

...
}

El fichero cabecera.h solo contiene las declaraciones de las plantillas (eventualmente cualquier
otra cabecera necesaria para el proyecto).
El fichero definiciones.cpp, que contiene las definiciones de las plantillas (puede incluir tambin
las definiciones de otras funciones del proyecto), es compilado en una unidad de compilacin
independiente del resto.
El resto de ficheros fuente: modulo-1.cpp; modulo2.cpp; modulo-3.cpp, etc. contienen
un #include al fichero de declaraciones. En este caso, el compilador no tiene que descartar las
copias redundantes de la definicin como en el caso anterior, pero a cambio debe localizar la nica
instancia de la definicin cada vez que sea necesaria. Observe que se necesita la
declaracin export para que la definicin sea accesible desde las otras unidades de compilacin.

3 export es una palabra clave [1] opcional que indica al compilador que la declaracin ser
accesible desde otras unidades de compilacin, y solo puede ser utilizada una vez con cada
declaracin (Recuerde el principio de una sola definicin
4.1.2).
Nota: en la pgina adjunta se incluyen algunas instrucciones para el compilador Borland (
BC++ 55 compiler switches).

4 Tipos locales como argumentos de plantillas


Tanto las funciones genricas como las clases genricas se instancian una sola vez para cada tipo.
El compilador utiliza un sistema de discriminacin basado en la equivalencia nombre-tipo, utilizado
en cada caso como parmetro de la plantilla, para decidir que plantillas deben ser instanciadas o
re-instanciadas.
Por ejemplo, considere los siguientes mdulos en el modelo de compilacin separada

// modulo1.cpp

// modulo2.cpp

#include <cabecera.h>

#include <cabecera.h>

...

...

{
class X { int data; };

class X {float data; };

...

...

f1(X);

// usar funcin

f1(X);

...
}

// usar funcin

...
}

En estos casos pueden ocurrir errores serios, dado que el tipo de X en el primer mdulo no
coincide con el tipo de X en el segundo. Sin embargo, la comprobacin nombre-tipo los hace
parecer como iguales. El corolario es que debe intentarse que la instanciacin implcita de
funciones (o clases) genricas no dependa de tipos locales.

4.12.2 Clases genricas


1 Sinopsis
Hemos indicado (
1.12) que las clases-plantilla, clases genricas, o generadores de clases,
son un artificio C++ que permite definir una clase mediante uno o varios parmetros. Este
mecanismo es capaz de generar la definicin de clases (instancias o especializaciones de la
plantilla) distintas, pero compartiendo un diseo comn. Podemos imaginar que una clase genrica
es un constructor de clases, que como tal, acepta determinados argumentos (no confundir con el
constructor de-una-clase, que genera objetos).
Para ilustrarlo recordemos la clase mVector que utilizamos al tratar la sobrecarga de operadores (
4.9.18d). En aquella ocasin los objetosmVector eran matrices cuyos elementos eran objetos
de la clase Vector; que a su vez representaban vectores de un espacio de dos dimensiones. El
diseo bsico de la clase es como sigue:
class mVector {
//
int dimension;
public:
Vector* mVptr;
mVector(int n = 1) {
//
dimension = n;
mVptr = new Vector[dimension];
}
~mVector() { delete [] mVptr; } //
Vector& operator[](int i) {
//
return mVptr[i];
}
void showmem (int);
//
};
void mVector::showmem (int i) {

definicin de la clase mVector

constructor por defecto

destructor
operador subndice
funcin auxiliar

if((i >= 0) && (i <= dimension)) mVptr[i].showV();


else cout << "Argumento incorrecto! pruebe otra vez" << endl;
}
El sistema de plantillas permite definir una clase genrica que instancie versiones de mVector para
matrices de cualquier tipo especificado por un parmetro. La ventaja de este diseo
parametrizado, es que cualquiera que sea el tipo de objetos utilizados por
las especializaciones de la plantilla, las operaciones bsicas son siempre las mismas (insercin,
borrado, seleccin de un elemento, etc).

2 Definicin
La definicin de una clase genrica tiene el siguiente aspecto:
template<lista-de-parametros> class nombreClase {
...
};

// Definicin

Una clase genrica puede tener una declaracin adelantada para ser declarada despus:
template<lista-de-parametros> class nombreClase;
...
template<lista-de-parametros> class nombreClase {
...
};

// Declaracin
// Definicin

pero recuerde que debe ser definida antes de su utilizacin [5], y la regla de una sola definicin (
4.1.2).
Observe que la definicin de una plantilla comienza siempre con template <...>, y que los
parmetros de la lista <...> no son valores, sino tipos de datos
. En la pgina adjunta se muestra
la gramtica C++ para el especificador template (
Gramtica).
La definicin de la clase genrica correspondiente al caso anterior es la siguiente:
template<class T> class mVector {
// L1 Declaracin de plantilla
int dimension;
public:
T* mVptr;
mVector(int n = 1) {
// constructor por defecto
dimension = n;
mVptr = new T[dimension];
}
~mVector() { delete [] mVptr; }
// destructor
T& operator[](int i) { return mVptr[i]; }
void showmem (int);
};
template<class T> void mVector<T>::showmem (int i) {
// L16:
if((i >= 0) && (i <= dimension)) mVptr[i].showV();
else cout << "Argumento incorrecto! pruebe otra vez" << endl;
}

Observe que aparte del cambio de la declaracin en L1, se han sustituido las ocurrencias
de Vector (un tipo concreto) por el parmetro T. Observe tambin la definicin de showmem() en
L16, que se realiza off-line con la sintaxis de una funcin genrica (
4.12.1).

2.1 Recordemos que en estas expresiones, el especificador class puede ser sustituido
por typename (
3.2.1e), de forma que la expresin L1 puede ser sustituida por:
tamplate<typename T> class mVector {
...
};

// L1-bis

Tambin que la definicin puede corresponder a una estructura (recordemos que son un tipo de
clase en las que todos sus miembros son pblicos). Por ejemplo:
template<class Arg> struct all_true : public unary_function<Arg, bool> {
bool operator()(const Arg& x){ return 1; }
};
2.2 Ejemplo-2
En la pgina adjunta se incluye un ejemplo operativo. Tomando como punto de partida la versin
definitiva de mVector (
4.9.18d1), se ha reproducido el mismo programa, pero cambiando el
diseo, de forma que mVector es ahora una clase genrica en vez de una clase especfica (
Ejemplo-2).

2.3 Miembros de clases genricas


Los miembros de las clases genricas se definen y declaran exactamente igual que los de clases
concretas.
Pero debemos sealar que las funciones-miembro son a su vez plantillas
parametrizadas (funciones genricas) con los mismos parmetros que la clase genrica a que
pertenecen.
Consecuencia de lo anterior es que si las funciones-miembro se definen fuera de la plantilla, sus
prototipos deberan presentar el siguiente aspecto:
template<class T> class mVector {
// Clase genrica
int dimension;
public:
T* mVptr;
template<class T> mVector<T>& operator=(const mVector<T>&);
template<class T> mVector<T>(int);
// constructor por defecto
template<class T> ~mVector<T>();
// destructor
template<class T> mVector<T>(const mVector<T>& mv); // constructorcopia
T& operator[](int i) { return mVptr[i]; }
template <class T> void showmem (int);
// funcin auxiliar
template <class T> void show ();
// funcin auxiliar
};
Sin embargo, no es exactamente as por diversas razones: la primera es que, por ejemplo, se
estara definiendo la plantilla showmem sin utilizar el parmetro T en su lista de argumentos (lo que

no est permitido
4.12.1). Otra es que no est permitido declarar los destructores como
funciones genricas. Adems, los especificadores <T> referidos a mVector dentro de la propia
definicin son redundantes.
Estas consideraciones hacen que los prototipos puedan ser dejados como sigue (los datos
faltantes pueden deducirse del contexto):
template<class T> class mVector {
// Clase genrica
int dimension;
public:
T* mVptr;
mVector& operator= (const mVector&);
mVector(int);
// constructor por defecto
~mVector();
// destructor
mVector(const mVector& mv);
// constructor-copia
T& operator[](int i) { return mVptr[i]; }
void showmem (int);
// funcin auxiliar
void show ();
// funcin auxiliar
};

2.3.1 Las definiciones de mtodos realizadas off-line (fuera del cuerpo de una plantilla) deben ser
declaradas explcitamente como funciones genricas (
4.12.1). Por ejemplo:
template <class T> void mVector<T>::showmem (int i) {
...
}
2.3.2 Ejemplo-3
Observe la sintaxis del siguiente ejemplo. Es igual que el anterior (Ejemplo-2
de que en este, las funciones-miembro se definen off-line (
Ejemplo-3).

), con la diferencia

2.3.3 Miembros estticos


Las clases genricas pueden tener miembros estticos (propiedades y mtodos). Posteriormente
cada especializacin dispondr de su propio conjunto de estos miembros. Estos miembros
estticos deben ser definidos fuera del cuerpo de la plantilla, exactamente igual que si fuesen
miembros estticos de clases concretas (
4.11.7).
Ejemplo:
template<class T> class mVector {
// Clase genrica
...
static T* mVptr;
static void showmem (int);
...
};
template<class T> T* mVector<T>::nVptr; // no es necesario poner static
template<class T> void mVector<T>::showmem(int x){ ... };

2.3.4 Mtodos genricos

Hemos sealado que, por su propia naturaleza, los mtodos de clases genricas son a su vez
(implcitamente) funciones genricas con los mismos parmetros que la clase, pero pueden ser
adems funciones genricas explcitas (que dependan de parmetros distintos de la plantilla a que
pertenecen):
template<class X> class A {
template<class T> void func(T& t);
genrica
...

// clase genrica
// mtodo genrico de clase

}
Segn es usual, la definicin del miembro genrico puede efectuarse de dos formas: on-line (en el
interior de la clase genrica
Ejemplo-1), y off-line (en el exterior
Ejemplo-6).

3 Observaciones:
La definicin de una clase genrica puede suponer un avance importante en la definicin de
clases relacionadas, sin embargo son pertinentes algunas advertencias:

3.1 Las clases genricas son entes de nivel superior a las clases concretas. Representan para
las clases normales (en este contexto preferimos llamarlas clases explcitas) lo mismo que las
funciones genricas a las funciones concretas. Como aquellas, solo tienen existencia en el cdigo.
Como el mecanismo de plantillas C++ se resuelve en tiempo de compilacin, ni en el fichero
ejecutable ni durante la ejecucin existe nada parecido a una clase genrica, solo existen
especializaciones (ver a continuacin 4
). En realidad la clase genrica que se ve en el cdigo,
acta como una especie de "macro" que una vez ejecutado su trabajo en la fase de compilacin,
desaparece de escena.
Como ha sealado algn autor, el mecanismo de plantillas representa una especie de polimorfismo
en tiempo de compilacin, similar al que proporciona la herencia de mtodos virtuales en tiempo de
ejecucin.

3.2 Aconsejamos realizar el diseo y una primera depuracin con una clase explcita antes de
convertirla en una clase genrica. Es ms fcil imaginarse el funcionamiento referido a un tipo
concreto que a entes abstractos. Adems es ms fcil entender los problemas que pueden
presentarse si se maneja una imagen mental concreta [1]. En estos casos es ms sencillo ir de lo
particular a lo general.

3.3 Contra lo que ocurre con las funciones genricas, en la instanciacin de clases genricas el
compilador no realiza ninguna suposicin sobre la naturaleza de los argumentos a utilizar, de
modo que se exige que sean declarados siempre de forma explcita. Por ejemplo:
mVector<char> mv1;
mVector mv2 = mv1;
mVector<char> mv2 = mv1;

// Error !!
// Ok.

Nota: como veremos a continuacin


, las clases genricas pueden tener argumentos por
defecto, por lo que en estos casos la declaracin puede no ser explcita sino implcita (referida

a los valores por defecto de los argumentos). La consecuencia es que en estos casos el
compilador tampoco realiza ninguna suposicin sobre los argumentos a utilizar.

3.4 Las clases genricas pueden ser utilizadas en los mecanismos de herencia. Por ejemplo:
template <class T> class Base { ... };
template <class T> class Deriv : public Base<T> {...};
Por ejemplo, es posible conseguir que una clase derive de una base o de otra segn las
circunstancias:
#include <iostream>
using namespace std;
class BaseA {
public:
BaseA() { cout << "BaseA"; }
};
class BaseB {
public:
BaseB() { cout << "BaseB"; }
};
template <typename T> class Derived : public T {
public:
Derived() { cout << "::Derived" << endl; }
};
int main() {
Derived<BaseA> ob1;
Derived<BaseB> ob2;
return EXIT_SUCCESS;
}
Como se habr figurado, la salida es:
BaseA::Derived
BaseB::Derived
El punto a destacar es que en el momento de instanciar el objeto es cuando se decide cual ser la
clase antecesora, lo que se consigue mediante la definicin de la clase Derived.

3.5 Con ciertas limitaciones, las clases genricas pueden simular el comportamiento de las
funciones virtuales (
4.11.8a). Consideremos el siguiente ejemplo [6]:
#include <iostream>
using namespace std;
class Base {
public:
virtual void foo() { cout << "foo in Base" << endl; }

void callFoo() { foo(); }


};
class Derived : public Base {
public:
void foo() { cout << "foo in Derived" << endl; }
};
int main() {
Derived obj;
obj.callFoo();
return 0;
}

// -> foo in Derived

El comportamiento de esta jerarqua puede ser mimetizado utilizando una clase genrica:
#include <iostream>
using namespace std;
template <typename T> class Base {
public:
void foo() { cout << "foo in Base" << endl; }
void callFoo() {
T* tPtr = static_cast<T*>(this);
tPtr->foo();
}
};
class Derived : public Base<Derived> {
public:
void foo() { cout << "foo in Derived" << endl; }
};
int main() {
Derived obj;
obj.callFoo();
return 0;
}

// -> foo in Derived

Como puede verse, el resultado es el mismo en ambos diseos. La ventaja del segundo frente al
primero es que el cdigo es ms compacto y rpido que el primero, debido a que no tiene
necesidad de utilizar el mecanismo de enlazado dinmico de las funciones virtuales (
1.4.4).
El programa tiene dos puntos interesantes: el primero en la definicin de la clase derivada, donde
podemos comprobar que puede derivar de una clase genrica, y que en este caso, le indicamos al
compilador que utilizaremos una del mismo tipo que la clase derivada. El segundo es la definicin
de la funcin callFoo. El puntero this (puntero a la clase Base) es convertido en un puntero a la
clase derivada, ya que el tipoDerived es pasado a la clase base a travs del parmetro T de la
plantilla. De esta forma, es invocada la funcin foo de la clase derivada, ya que el puntero seala
a dicho objeto.

3.6 Los typedef (


3.2.1a) son muy adecuados para acortar la notacin de objetos de clases
genricas cuando se trata de declaraciones muy largas o no interesan los detalles. Por ejemplo:
typedef basic_string <char> string;
ms tarde, para obtener un objeto puede escribirse:
string st1;

// equivale a: basic_string <char> st1;

4 Instanciacin (obtener especializaciones)


Al tratar las funciones genricas vimos que el compilador genera el cdigo apropiado en cuanto
aparece una invocacin a la funcin. Por ejemplo, si max(a, b) es una funcin genrica:
UnaClase a, b, m;
m = max(a, b);

// invoca especializacin para objetos UnaClase

Para utilizar un objeto de una clase genrica, es necesario previamente instanciar la


especializacin de la clase que instanciar a su vez el objeto. La primera instanciacin (de la clase
concreta) se realiza mediante la invocacin de la clase genrica utilizando argumentos [4]. Por
ejemplo:
mVector<Vector> mV1, mV2;
mVector<Complejo> mC1, mC2;

// Ok. matrices de Vectores


// Ok. matrices de Complejos

En este caso, durante la compilacin, la primera sentencia crea una instancia (funcin-clase
4.11.5) de mVector especfica para miembrosVector; que a su vez crear un objeto-clase en
tiempo de ejecucin que ser el encargado de instanciar los objetos mV1 y mV2. En este
contextomVector<Vector> representa la clase que genera los objetos mV1 y mV2. La segunda
sentencia es anloga, aunque para complejos.
Observe que si mVector es una clase genrica, el identificador mVector debe ir acompaado
siempre por un tipo T entre ngulos ( <T> ), ya que los ngulos vacos ( <> ) no pueden aparecer
solos, excepto en algunos casos de la definicin de la plantilla o cuando tenga valores por defecto.
Nota: como veremos a continuacin
, las clases genricas pueden tener argumentos por
defecto, en cuyo caso, el tipo T puede omitirse, pero no los ngulos <>. Por ejemplo:
template<class T = int> class mVector {/* ... */}; // valor int por
defecto
...
mVector<char> mv1;
// Ok. argumento char explcito
mVector<> mv2;
// Ok. argumento int implcito (valor por defecto)

Cada instancia de una clase genrica es realmente una clase, y sigue las reglas generales de las
clases. Como se ver a continuacin
, pueden recibir referencias y punteros, y dispone de su

propia versin de todos los miembros estticos si los hubiere (


4.11.7). Estas clases son
denominadas implcitas, para distinguirlas de las definidas "manualmente", que se
denominan explcitas.
La primera vez que el compilador encuentra una sentencia del tipo mVector<Vector>, crea la
funcin-clase para dicho tipo; es el punto de instanciacin. Con objeto de que solo exista una
definicin de la clase, si existen ms ocurrencias de este mismo tipo, las funciones-clase
redundantes son eliminados por el enlazador. Por la razn inversa, si el compilador no encuentra
ninguna razn para instanciar una clase (generar la funcin-clase), esta generacin no se producir
y no existir en el cdigo ninguna instancia de la plantilla.

5 Evitar la generacin automtica de especializaciones (especializaciones explcitas)


Lo mismo que ocurre con las funciones genricas (
4.12.1a), en las clases genricas tambin
puede evitarse la generacin de versiones implcitas para tipos concretos proporcionando una
especializacin explcita. Por ejemplo:
class mVector<T*> { ... };
...
class mVector<char*> { ... };

// L1: definicin genrica


// L2: definicin especfica

ms tarde, las declaraciones del tipo


mVector<char*> mv1, mv2;
generar objetos utilizando la definicin especfica proporcionada por L2. En este
caso mv1 y mv2 sern matrices alfanumricas (cadenas de caracteres).
Observe que la definicin explcita comporta dos requisitos:

Aunque es una versin especfica (para un tipo concreto), se utiliza la sintaxis de


plantilla: class mVector<...> {...};
Se ha sustituido el tipo genrico <T*> por un tipo concreto <char*>.
Resulta evidente que una definicin especfica, como la includa en L2, solo tiene sentido si se
necesitan algunas modificaciones en el diseo L1 cuando la clase se refiera a
tipos char* (punteros-a-char)

6 Argumentos de la plantilla
La declaracin de clases genricas puede incluir una lista con varios parmetros. Estos pueden ser
casi de cualquier tipo: complejos; fundamentales (
2.2), por ejemplo un int, o incluso otra clase
genrica (plantilla). Adems, en todos los casos pueden presentar valores por defecto. Ejemplo:
template<class T, int dimension = 128> class mVector { ... };
En ocasiones, cuando el argumento de una clase genrica es a su vez otra clase genrica debe
tenerse en cuenta cierta singularidad sintctica. Considere el siguiente ejemplo:
template <class T> class Point {
public:

T x, y, z;
};
template <class T> class Vector {
public:
T p1, p2;
};
int main() {
// ================
Vector<Point<int> > v1;
// oberseve el espacio > >
return 0;
}
En estos casos, algunos compiladores necesitan que se incluya el espacio entre los smbolos > en
la declaracin del argumento. De no hacerlo as, el compilador podra interpretarlo como el
operador de manejo de bits (left shift
4.9.3) con el consiguiente mensaje de error. En el caso
del compilador GNU cpp, el mensaje obtenido es muy ilustrativo: 12 D:\LearnC\main.cpp
`>>' should be `> >' within a nested template argument list.

6.1 En la instanciacin de clases genricas, los valores de los parmetros que no sean tipos
complejos deben ser constantes o expresiones constantes (
3.2.3). Por ejemplo:
const int K = 128;
int i = 256;
mVector<int, 2*K> mV1;
mVector<Vector, i> mV2;

// OK
// Error: i no es constante

Este tipo de parmetros constantes son adecuados para establecer tamaos y lmites. Por
ejemplo:
template <class T, int max = 128 > class Matriz {
...
int dimension;
T* ptr;
Matriz (int d = 0) {
// constructor
dimension = d > max ? max : d;
ptr = new T [dimension];
}
...
};
Sin embargo, por su propia naturaleza de constantes, cualquier intento posterior de alterar su valor
genera un error.
Cuando es necesario pasar valores numricos, enteros o fraccionarios, para los que pueda existir
ambigedad en el tipo, se aconseja incluir sufijos (
3.2.3b y
3.2.3c). Por ejemplo, en el caso
de instanciar un objeto para la plantilla anterior:
Matriz<Vector, 256U> mV1;

6.2 Los argumentos pueden ser otras plantillas, pero solo clases genricas.; Las funciones
genricas no estn permitidas:
template <class T, template<class X> class C> class MatrizC { // Ok.
...
};
template <class T, template<class X> void Func(X a)> class MatrizF { //
Error!!
...
};

6.3 Tenga en cuenta que no existe algo parecido a un mecanismo de "sobrecarga" de las clases
genricas paralelo al de las funciones genricas. Por ejemplo, las siguientes declaraciones
producen un error de compilacin.
template<class T> class mVector { ... };
template<class T, int dimension> class mVector { ... };
Error: Number of template parameters does not match in redeclaration of
'Matriz<T>'.

template<class T, class D> class mVector { ... };


template<class T, int dimension> class mVector { ... };
Error: Argument kind mismatch in redeclaration of template parameter
'dimension'.

7 La cuestin de los "Tipos" en las plantillas


La plantilla que ve el programador en el cdigo no tiene existencia cuando termina la compilacin.
En el ejecutable solo existen funciones-clase especficas (
4.11.5) que darn lugar a objetosclase durante la ejecucin. Esto hace que no sea posible obtener el "Tipo" de una plantilla con el
operador typeid (
4.9.14); en cualquier caso, hay que aplicarlo sobre una instancia concreta.
Por ejemplo:
template <class T, int size = 0 > class Matriz { ... };
...
const type_info & ref1 = typeid(Matriz);
// Error!!
Con este intento compilador muestra un mensaje muy esclarecedor: Error ... Cannot use
template 'Matriz<T, max>' without specifying specialization parameters. En
cambio, el cdigo:
const type_info & ref = typeid(Matriz<int, 5>);
cout << "El tipo es: " << ref.name() << endl;
Produce la siguiente salida:

// Ok.

El tipo es: Matriz<int,5>


Es el mismo resultado que para el objeto m1:
Matriz<int, 5> m1;
Matriz<int, 2> m2;
Matriz<char, 5> m3;
Tenga en cuenta que m2 es tipo Matriz<int, 2> y que m3 es tipo Matriz<char, 5>. Para el
compilador todos ellos son tipos distintos. Justamente debido a esto, para conseguir clases lo ms
genricas posibles conviene circunscribir al mnimo los argumentos de la plantilla, ya que el
lenguaje C++ es fuertemente tipado (
2.2), y cada nuevo argumento aumenta la posibilidad de
que los tipos sean distintos entre si, lo que conduce a una cierta rigidez posterior [3]. Por ejemplo,
en el caso anterior m1 y m2 son tipos distintos, en consecuencia quizs no se puedan efectuar
determinadas operaciones entre ellos (m1 + m2) a pesar que el operador suma + est definido en
la plantilla. En cambio, si la definicin de esta es del tipo:
template < T > class Matriz { ...... };
Y dejamos el argumento size para el constructor, las matrices m1 y m2:
Matriz<int> m1(5);
Matriz<int> m2(2);
Son del mismo tipo y seguramente se podr efectuar la operacin m1 + m2.
7.1 Ejemplo-4
El ejemplo adjunto muestra claramente esta influencia, as como las diferencias obtenidas con
varias formas del programa. En la primera, se utiliza una clase explcita; en la segunda esta clase
se transforma en una clase genrica, y en la tercera se incluye un segundo argumento que evita
incluirlo en el constructor (
Ejemplo-4).

8 Punteros y referencias a clases implcitas


Como hemos sealado, las clases implcitas gozan de todas las prerrogativas de las explcitas,
incluyendo por supuesto la capacidad de definir punteros y referencias. En este sentido no se
diferencian en nada de aquellas. La nica precaucin es tener presente la cuestin de los tipos a la
hora de efectuar asignaciones y no perder de vista que la plantilla es una abstraccin que
representa mltiples clases (tipos), cada una representada por argumentos concretos.
Consideremos la clase genrica Matriz ya utilizada anteriormente (
es:

Ejemplo-4) cuya declaracin

template <class T, int dim =1> class Matriz { /* ... */};


La definicin de punteros y referencias sera como sigue:
Matriz<int,5> m1;
Matriz<char,5> m2;
Matriz<char> m3;

// Ok. Tres objetos (matrices)


// de tipos
// distintos.

...
Matriz<int,5>* ptrMi5 = &m2
Matriz<char,5>* ptrMch5 = &m2;
Matriz<char,1>* ptrMch1 = &m2;
Matriz<char,1>* ptrMch1 = &m3;
ptrMch5->show();
puntero
Matriz<char> m4 = *ptrMch1;

//
//
//
//
//

Error!! tipos distintos


Ok.
Error!! tipos distintos
Ok.
Ok. invocacin de mtodo mediante

// L10: Ok asignacin mediante puntero

Matriz<char>* mPtr = new Matriz<char>;


persistente

// Ok. puntero a objeto

void (Matriz<char,5>::* fptr1)();


fptr1 = &Matriz<char,5>::show;
(m3.*fptr1)();
(m2.*fptr1)();
deferenciando su puntero

//
//
//
//

L11: Ok. declara puntero a mtodo


Ok. asignacin
Error!! tipo de m3 incorrecto
Ok. invocacin de mtodo

Matriz<char,5>& refMch5 = m2;


Matriz<char>& refMch1 = m4;
refMch5.show();
referencia al objeto

// Ok. referencia
// Ok.
// Ok. invocacin de mtodo mediante

Comentario
Observe que la asignacin L10 exige que la clase genrica Matriz tenga definido el constructorcopia y que est sobrecargado el operador de indireccin * para los miembros de la clase (
4.9.11).
Merecen especial atencin las sentencias L11/L14. En este caso se trata de punteros-a-miembros
de clases implcitas. El tipo de clase est definido en los parmetros. Observe que fptr1 es
puntero-a-mtodo-de-clase-Matriz<char, 5> [2], y no puede referenciar un mtodo de m3, que es
un objeto de tipo Matriz<char,1>.
Nota: para comprender cabalmente las sentencias anteriores, puede ser de gran ayuda un
repaso previo a los captulos: Punteros a Clases (
4.2.1f) y Punteros a Miembros (
4.2.1g).
8.2 Ejemplo-5
En este ejemplo se muestra la utilizacin de punteros a clases implcitas, as como de la
sobrecarga del operador de indireccin (
Ejemplo-5).

Clases genricas
Ejemplo
En realidad se trata de la continuacin de un ejemplo anterior (
Ejemplo-4). All se present un
problema con una asignacin entre objetos de tipo distinto. Aqu se muestra una posible solucin
mediante un "Casting" (
4.9.9d) que permita vencer (por la "fuerza bruta") la resistencia del
mecanismo de comprobacin de tipos del compilador.

El ejemplo muestra tambin una serie de aspectos interesantes sobre el uso de punteros a clases
genricas y a sus miembros. Algunos de los operadores definidos para la clase no son realmente
necesarios, se han mantenido para respetar al mximo el diseo original. Sin embargo, ha sido
necesario incluir una definicin sobrecargada del operador de indireccin * para poder utilizarlo
con miembros de la clase.

#include <iostream>
using namespace std;
template <class T, int dim = 1> class Matriz {
int dimension;
public:
T* mptr;
Matriz operator* () { return *this; }
// Operador de indireccin
Matriz& operator=(const Matriz& m) {
// Operador de asignacin
delete [] mptr;
dimension = m.dimension;
mptr = new T [dimension];
for(int i = 0; i<dimension; i++) mptr[i]= m.mptr[i];
return *this;
}
Matriz operator+ (const Matriz& m) {
// operador suma binaria
Matriz mt(m.dimension + this->dimension);
for(int i = 0; i<this->dimension; i++) mt.mptr[i]= this->mptr[i];
for(int j = 0; j<this->dimension; j++) mt.mptr[j+i]= m.mptr[j];
return mt;
};
Matriz() {
// constructor por defecto
dimension = dim > 64000 ? 64000 : dim;
mptr = new T [dimension];
}
~Matriz() { delete [] mptr; }
// destructor
Matriz(const Matriz& m) {
// constructor-copia
dimension = m.dimension;
mptr = new T [dimension];
for(int i = 0; i<dimension; i++) mptr[i]= m.mptr[i];
}
T& operator[](int i) { return mptr[i]; }
void show () {
// Funcin auxiliar
cout << "Matriz de: " << dimension << " elementos." << endl;
for (int i = 0; i<dimension; i++) {
cout << i << "- " << mptr[i] << "; ";
}
cout << "\n";
}
};
void main() {
// =====================
Matriz<char, 6> m2;
// M.1:
m2[0] ='A'; m2[1] ='E'; m2[2] ='I'; m2[3] ='O'; m2[4] ='U'; m2[5]
='\0';
Matriz<char, 6>* ptrMc6 = &m2;
// M.3: Ok. puntero a clase
ptrMc6->show();
// M:4: Ok. invocacin de mtodo
(salida 1)
//Matriz<char> m3 = m2;
M.5: antiguo Error

//Matriz<char, 6> m3 = *ptrMc6;

M.6: antigua solucin

Matriz<char> m4 = *(reinterpret_cast<Matriz<char,1>*> (ptrMc6));


M.8:
Matriz<char, 6>* ptrMc6b = &m4;
// M.9: Error!!
Matriz<char>* ptrMc1a = &m4;
// M.10: Ok.
ptrMc1a->show();
// M.11: Ok. (salida 2)

//

char* Matriz<char,6>::* mpt1;


mpt1 = &Matriz<char, 6>::mptr;
cout << m2.*mpt1 << endl;
cout << (m2.*mpt1)[0] << endl;

//
//
//
//

M.13 puntero a miembro


M.14: Ok. asignacin a un miembro
M.15: Salida 3
M.16: Salida 4

void (Matriz<char,6>::* fpt1)();


fpt1 = &Matriz<char,6>::show;
(m2.*fpt1)();

// M.18: puntero a funcin miembro


// M.20: Salida 5

}
Salida:
Matriz de: 6 elementos.
S1
0- A; 1- E; 2- I; 3- O; 4- U; 5- ;
Matriz de: 6 elementos.
S2
0- A; 1- E; 2- I; 3- O; 4- U; 5- ;
AEIOU
S3
A
S4
Matriz de: 6 elementos.
S5
0- A; 1- E; 2- I; 3- O; 4- U; 5- ;
Comentario
En M.1 se instancia una matriz de caracteres de 6 elementos, que son inicializados a valores
concretos en M.2. Por conveniencia para la salida S3, se ha alargado el tamao original de esta
matriz, aadindole un elemento con un carcter nulo.
En M.3 se declara e inicia el puntero ptrMc6; se trata de un puntero a la clase implcita
Matriz<char, 6> que seala al objeto m2 (cuyo tipo coincide con las exigencias del puntero). En la
lnea siguiente se utiliza este puntero para invocar el mtodo show del objeto (salida S1).
La sentencia M.5 es anloga a la que causaba el error en el ejemplo original. El error es debido a
que se pretende la invocacin del constructor-copia con un argumento de tipo distinto al esperado.
M.6 muestra la solucin dada en aquel caso. En realidad se trata de una solucin a medias, ya
que m3 se crea con tipo distinto al pretendido inicialmente (hemos mantenido aqu ambas
sentencias como recordatorio y comparacin con la nueva solucin).
Las sentencias M8 es la nueva solucin. Observe que el puntero ptrMc6 creado en M.3, seala a
la matriz m2 (que se pretende acte como Rvalue en la asignacin fallida de M.5). Ahora se vuelve
a intentar la asignacin, pero sealando el objeto m2 mediante la indireccin de su puntero.
Observe que una asignacin "tal cual" del tipo:
Matriz<char> m4 = *ptrMc6;

no hubiese sido posible; aunque bajo otra forma sintctica, sigue siendo la misma asignacin de
M.5. Para salvar el obstculo obligamos al compilador a realizar una conversin forzada del tipo
sealado por ptrMc6 (reinterpret_cast
4.9.9d). En M.11 se muestra finalmente el resultado
obtenido (salida S2).
En M.9 se crea otro puntero anlogo al de M.3, y se pretende que seale al objeto m4,
producindose un error. La razn vuelve a estar en la cuestin de los tipos. El nuevo objeto es
puntero a la clase implcita Matriz<char, 6>, mientras que el objeto m4 que se pretende sealar es
de tipo Matriz<char, 1>.
En M.10 se crea e inicia sin novedad un puntero de tipo adecuado al objeto que se pretende
sealar, y en M.11 se utiliza para invocar el mtodoshow sobre el objeto m4, lo que produce la
salida S2. Es muy significativo que para el compilador, m4 es tipo Matriz<char, 1>, a pesar de que
"realmente" responde a una matriz de 6 elementos. Este es justamente uno de los peligros de un
modelado "por la tremenda" como el utilizado. Probablemente, si se tratara de un programa
grande, antes que despus tendramos problemas (C++ no necesita que el programador se busque
problemas engaando al compilador, se basta l solito para encontrarlos :-)
En M.13 se define un puntero a miembro mpt1 (
4.2.1g). Concretamente al tipo puntero-achar (char*). En la siguiente sentencia este puntero es asignado al nico miembro de la clase
implcita que responde a la especificacin, el miembro mptr. A pesar de la asignacin, en este
momentompt1 es en realidad un puntero genrico (es una curiosa caracterstica de los punteros a
miembros no estticos). Seala al miembro de cualquierobjeto que responda a su especificacin.
Nota: es importante sealar que este puntero solo puede sealar a miembros de su propio tipo. Si,
por ejemplo, se instancia un objeto:
Matriz<int, 6> m1;
La asignacin:
mpt1 = &Matriz<int, 6>::mptr;
producira un error, ya que el puntero mpt1 y el miembro sealado son de tipos distintos. Para
sealar al miembro mptr del objeto m1, es preciso que el puntero responda a la siguiente
declaracin:
int* Matriz<int,6>::* mpt1;
Al final se aaden algunas consideraciones sobre la cuestin aqu apuntada.

La sentencia M.15 aplica la indireccin del puntero "genrico" mpt1 sobre el objeto m2 [1] (observe
la notacin utilizada). En realidad pedimos al compilador que muestre el objeto sealado por el
puntero. Recordemos que el objeto es un puntero-a-char, y en estas circunstancias, se muestra la
cadena hasta el primer carcter nulo (salida S3); esta caracterstica es herencia del C clsico,
donde las cadenas alfanumricas terminan en un carcter nulo.
En M.16 se ha utilizado la notacin adecuada para obtener un solo elemento (salida S4).
Utilizando un subndice adecuado puede accederse cualquier otro.

La sentencia M.18 es parecida a M.13, aunque en este caso declara un puntero a funcin-miembro
(fpt1). En la siguiente, el puntero es asignado al mtodo show de la clase implcita
correspondiente. Finalmente, el mtodo (accedido mediante la indireccin de su puntero), es
aplicado sobre el objeto m2, dando lugar a la salida S5.
En el ejemplo se han utilizado dos de las tres posibilidades de invocacin de un mtodo sobre un
objeto:
ptrMc6->show();
indirecto)
(m2.*fpt1)();
m2.show();

// invocacin mediante puntero-a-clase (selector


// invocacin mediante puntero-a-miembro (indireccin)
// invocacin mediante selector directo de miembro

Corolario:
La cuestin suscitada con motivo del puntero-a-miembro mpt1 plantea en realidad una interesante
cuestin sobre los punteros a miembros de clases genricas. El asunto puede ser esquematizado
como sigue:
template <class T> class Cls {
// clase genrica
T* mem;
// puntero-a-tipo de la clase ( puntero-a-Cls<T> )
Cls<T>*
...
}
...
Cls<T1> obj1;
// instancia de clase implcita Cls<T1>
Cls<T2> obj2;
// instancia de clase implcita Cls<T2>
... etc.
La cuestin es que si queremos un puntero al miembro mem, debemos declararlo con la sintaxis
adecuada:
T1* Cls<T1>::* ptr1 = &Cls<T1>::mem;
T2* Cls<T2>::* ptr2 = &Cls<T2>::mem;
...
// etc.

// para clase Cls<T1>


// para clase Cls<T2>

Evidentemente se necesitan tantas declaraciones como especializaciones distintas puedan


derivarse de la plantilla. En realidad se necesitara una definicin parametrizada del puntero a
miembro. Algo parecido a:
T* Cls<T>::* ptrn = &Cls<T>::mem;

// para clase genrica Cls<T>

Sin embrago, el lenguaje no permite este tipo de definiciones siendo T una variable (las plantillas
solo pueden definirse para funciones y clases).

4.12.2a Clases genricas II


1209

Potrebbero piacerti anche