Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.12.2  Clases genéricas

§1  Sinopsis

Hemos indicado ( 1.12) que las clases-plantilla, clases genéricas, o generadores de clases, son un artificio C++ que permite definir una clase mediante uno o varios parámetros. Este mecanismo es capaz de generar la definición de clases (instancias o especializaciones de la plantilla) distintas, pero compartiendo un diseño común. Podemos imaginar que una clase genérica 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 ocasión los objetos mVector eran matrices cuyos elementos eran objetos de la clase Vector; que a su vez representaban vectores de un espacio de dos dimensiones. El diseño básico de la clase es como sigue:

class mVector {                   // definición de la clase mVector
  int dimension;
  public:
  Vector* mVptr;
  mVector(int n = 1) {            // constructor por defecto
    dimension = n;
    mVptr = new Vector[dimension];
  }
  ~mVector() { delete [] mVptr; } // destructor
  Vector& operator[](int i) {     // operador subíndice
    return mVptr[i];
  }
  void showmem (int);             // función auxiliar
};

void mVector::showmem (int i) {
  if((i >= 0) && (i <= dimension)) mVptr[i].showV();
  else cout << "Argumento incorrecto! pruebe otra vez" << endl;
}

El sistema de plantillas permite definir una clase genérica que instancie versiones de mVector para matrices de cualquier tipo especificado por un parámetro.  La ventaja de este diseño parametrizado, es que cualquiera que sea el tipo de objetos utilizados por las especializaciones de la plantilla, las operaciones básicas son siempre las mismas (inserción, borrado, selección de un elemento, etc).

§2  Definición

La definición de una clase genérica tiene el siguiente aspecto:

template<lista-de-parametros> class nombreClase {  // Definición
   ...
};

Una clase genérica puede tener una declaración adelantada para ser declarada después:

template<lista-de-parametros> class nombreClase;   // Declaración
...
template<lista-de-parametros> class nombreClase {  // Definición
   ...
};

pero recuerde que debe ser definida antes de su utilización [5], y la regla de una sola definición ( 4.1.2).

Observe que la definición de una plantilla comienza siempre con template <...>, y que los parámetros de la lista <...> no son valores, sino tipos de datos . En la página adjunta se muestra la gramática C++ para el especificador template ( Gramática).

La definición de la clase genérica correspondiente al caso anterior es la siguiente:

template<class T> class mVector {     // L1 Declaración 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 declaración en L1, se han sustituido las ocurrencias de Vector (un tipo concreto) por el parámetro T. Observe también la definición de showmem() en L16, que se realiza off-line con la sintaxis de una función genérica ( 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 expresión L1 puede ser sustituida por:

tamplate<typename T> class mVector {      // L1-bis
  ...
};

También que la definición puede corresponder a una estructura (recordemos que son un tipo de clase en las que todos sus miembros son públicos). 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 página adjunta se incluye un ejemplo operativo. Tomando como punto de partida la versión definitiva de mVector ( 4.9.18d1), se ha reproducido el mismo programa, pero cambiando el diseño, de forma que mVector es ahora una clase genérica en vez de una clase específica ( Ejemplo-2).

§2.3  Miembros de clases genéricas

Los miembros de las clases genéricas se definen y declaran exactamente igual que los de clases concretas. Pero debemos señalar que las funciones-miembro son a su vez plantillas parametrizadas (funciones genéricas) con los mismos parámetros que la clase genérica a que pertenecen.

Consecuencia de lo anterior es que si las funciones-miembro se definen fuera de la plantilla, sus prototipos deberían presentar el siguiente aspecto:

template<class T> class mVector {           // Clase genérica
  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); // constructor-copia
  T& operator[](int i) { return mVptr[i]; }
  template <class T> void showmem (int);   // función auxiliar
  template <class T> void show ();         // función auxiliar
};

Sin embargo, no es exactamente así por diversas razones: la primera es que, por ejemplo, se estaría definiendo la plantilla showmem sin utilizar el parámetro 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 genéricas. Además, los especificadores <T> referidos a mVector dentro de la propia definición 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 genérica
  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);                     // función auxiliar
  void show ();                           // función auxiliar
};


§2.3.1  Las definiciones de métodos realizadas off-line (fuera del cuerpo de una plantilla) deben ser declaradas explícitamente como funciones genéricas ( 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 ), con la diferencia de que en este, las funciones-miembro se definen off-line  ( Ejemplo-3).

§2.3.3 Miembros estáticos

Las clases genéricas pueden tener miembros estáticos (propiedades y métodos). Posteriormente cada especialización dispondrá de su propio conjunto de estos miembros. Estos miembros estáticos deben ser definidos fuera del cuerpo de la plantilla, exactamente igual que si fuesen miembros estáticos de clases concretas ( 4.11.7).

Ejemplo:

template<class T> class mVector {        // Clase genérica
  ...
  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  Métodos genéricos

Hemos señalado que, por su propia naturaleza, los métodos de clases genéricas son a su vez (implícitamente) funciones genéricas con los mismos parámetros que la clase, pero pueden ser además funciones genéricas explícitas (que dependan de parámetros distintos de la plantilla a que pertenecen):

template<class X> class A {           // clase genérica
  template<class T> void func(T& t);  // método genérico de clase genérica
  ...

}

Según es usual, la definición del miembro genérico puede efectuarse de dos formas: on-line (en el interior de la clase genérica Ejemplo-1), y off-line (en el exterior Ejemplo-6).

§3  Observaciones:

La definición de una clase genérica puede suponer un avance importante en la definición de clases relacionadas, sin embargo son pertinentes algunas advertencias:


§3.1
  Las clases genéricas son entes de nivel superior a las clases concretas. Representan para las clases normales (en este contexto preferimos llamarlas clases explícitas) lo mismo que las funciones genéricas a las funciones concretas. Como aquellas, solo tienen existencia en el código. Como el mecanismo de plantillas C++ se resuelve en tiempo de compilación, ni en el fichero ejecutable ni durante la ejecución existe nada parecido a una clase genérica, solo existen especializaciones (ver a continuación §4 ). En realidad la clase genérica que se ve en el código, actúa como una especie de "macro" que una vez ejecutado su trabajo en la fase de compilación, desaparece de escena.

Como ha señalado algún autor, el mecanismo de plantillas representa una especie de polimorfismo en tiempo de compilación, similar al que proporciona la herencia de métodos virtuales en tiempo de ejecución.


§3.2  Aconsejamos realizar el diseño y una primera depuración con una clase explícita antes de convertirla en una clase genérica. Es más fácil imaginarse el funcionamiento referido a un tipo concreto que a entes abstractos. Además es más fácil entender los problemas que pueden presentarse si se maneja una imagen mental concreta [1]. En estos casos es más sencillo ir de lo particular a lo general.


§3.3  Contra lo que ocurre con las funciones genéricas, en la instanciación de clases genéricas el compilador no realiza ninguna suposición sobre la naturaleza de los argumentos a utilizar, de modo que se exige que sean declarados siempre de forma explícita. Por ejemplo:

mVector<char> mv1;
mVector mv2 = mv1;        // Error !!
mVector<char> mv2 = mv1;  // Ok.

Nota: como veremos a continuación , las clases genéricas pueden tener argumentos por defecto, por lo que en estos casos la declaración puede no ser explícita sino implícita (referida a los valores por defecto de los argumentos). La consecuencia es que en estos casos el compilador tampoco realiza ninguna suposición sobre los argumentos a utilizar.


§3.4  Las clases genéricas 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 según 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 definición de la clase Derived.


§3.5  Con ciertas limitaciones, las clases genéricas 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();   // -> foo in Derived
   return 0;
}

El comportamiento de esta jerarquía puede ser mimetizado utilizando una clase genérica:

#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();    // -> foo in Derived
   return 0;
}

Como puede verse, el resultado es el mismo en ambos diseños. La ventaja del segundo frente al primero es que el código es más compacto y rápido que el primero, debido a que no tiene necesidad de utilizar el mecanismo de enlazado dinámico de las funciones virtuales ( 1.4.4).

El programa tiene dos puntos interesantes: el primero en la definición de la clase derivada, donde podemos comprobar que puede derivar de una clase genérica, y que en este caso, le indicamos al compilador que utilizaremos una del mismo tipo que la clase derivada.  El segundo es la definición de la función callFoo. El puntero this (puntero a la clase Base) es convertido en un puntero a la clase derivada, ya que el tipo Derived es pasado a la clase base a través del parámetro T de la plantilla. De esta forma, es invocada la función foo de la clase derivada, ya que el puntero señala a dicho objeto.


§3.6 
Los typedef   ( 3.2.1a) son muy adecuados para acortar la notación de objetos de clases genéricas cuando se trata de declaraciones muy largas o no interesan los detalles. Por ejemplo:

typedef basic_string <char> string;

más tarde, para obtener un objeto puede escribirse:

string st1;    // equivale a: basic_string <char> st1;

§4  Instanciación (obtener especializaciones)

Al tratar las funciones genéricas vimos que el compilador genera el código apropiado en cuanto aparece una invocación a la función.  Por ejemplo, si max(a, b) es una función genérica:

UnaClase a, b, m;
m = max(a, b);       // invoca especialización para objetos UnaClase


Para utilizar un objeto de una clase genérica, es necesario previamente instanciar la especialización de la clase que instanciará a su vez el objeto.  La primera instanciación (de la clase concreta) se realiza mediante la invocación de la clase genérica utilizando argumentos [4]. Por ejemplo:

mVector<Vector> mV1, mV2;       // Ok. matrices de Vectores
mVector<Complejo> mC1, mC2;     // Ok. matrices de Complejos


En este caso, durante la compilación, la primera sentencia crea una instancia (función-clase 4.11.5) de mVector específica para miembros Vector; que a su vez creará un objeto-clase en tiempo de ejecución que será el encargado de instanciar los objetos mV1 y mV2. En este contexto mVector<Vector> representa la clase que genera los objetos mV1 y mV2. La segunda sentencia es análoga, aunque para complejos.

Observe que si mVector es una clase genérica, el identificador mVector debe ir acompañado siempre por un tipo T entre ángulos ( <T> ), ya que los ángulos vacíos ( <> ) no pueden aparecer solos, excepto en algunos casos de la definición de la plantilla o cuando tenga valores por defecto.

Nota: como veremos a continuación , las clases genéricas 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 explícito
mVector<> mv2;       // Ok. argumento int implícito (valor por defecto)


Cada instancia de una clase genérica es realmente una clase, y sigue las reglas generales de las clases. Como se verá a continuación , pueden recibir referencias y punteros, y dispone de su propia versión de todos los miembros estáticos si los hubiere ( 4.11.7). Estas clases son denominadas implícitas, para distinguirlas de las definidas "manualmente", que se denominan explícitas.

La primera vez que el compilador encuentra una sentencia del tipo mVector<Vector>, crea la función-clase para dicho tipo; es el punto de instanciación. Con objeto de que solo exista una definición de la clase, si existen más ocurrencias de este mismo tipo, las funciones-clase redundantes son eliminados por el enlazador. Por la razón inversa, si el compilador no encuentra ninguna razón para instanciar una clase (generar la función-clase), esta generación no se producirá y no existirá en el código ninguna instancia de la plantilla.

§5  Evitar la generación automática de especializaciones (especializaciones explícitas)

Lo mismo que ocurre con las funciones genéricas ( 4.12.1a), en las clases genéricas también puede evitarse la generación de versiones implícitas para tipos concretos proporcionando una especialización explícita. Por ejemplo:

class mVector<T*> { ... };      // L1: definición genérica
...
class mVector<char*> { ... };   // L2: definición específica

más tarde, las declaraciones del tipo

mVector<char*> mv1, mv2;

generará objetos utilizando la definición específica proporcionada por L2. En este caso mv1 y mv2 serán matrices alfanuméricas (cadenas de caracteres).

Observe que la definición explícita comporta dos requisitos:

  • Aunque es una versión específica (para un tipo concreto), se utiliza la sintaxis de plantilla:  class mVector<...> {...};
  • Se ha sustituido el tipo genérico <T*> por un tipo concreto <char*>.

Resulta evidente que una definición específica, como la incluída en L2, solo tiene sentido si se necesitan algunas modificaciones en el diseño L1 cuando la clase se refiera a tipos char* (punteros-a-char)

§6  Argumentos de la plantilla

La declaración de clases genéricas puede incluir una lista con varios parámetros. Estos pueden ser casi de cualquier tipo: complejos; fundamentales ( 2.2), por ejemplo un int, o incluso otra clase genérica (plantilla). Además, 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 genérica es a su vez otra clase genérica debe tenerse en cuenta cierta singularidad sintáctica.  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 símbolos > en la declaración del argumento. De no hacerlo así, el compilador podría 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 instanciación de clases  genéricas, los valores de los parámetros 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;              // OK
mVector<Vector, i> mV2;             // Error: i no es constante

Este tipo de parámetros constantes son adecuados para establecer tamaños y límites.  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 numéricos, enteros o fraccionarios, para los que pueda existir ambigüedad 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 genéricas.; Las funciones genéricas no están 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 genéricas paralelo al de las funciones genéricas.  Por ejemplo, las siguientes declaraciones producen un error de compilación.

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 cuestión de los "Tipos" en las plantillas

La plantilla que ve el programador en el código no tiene existencia cuando termina la compilación. En el ejecutable solo existen funciones-clase específicas ( 4.11.5) que darán lugar a objetos-clase durante la ejecución. 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 código:

const type_info & ref = typeid(Matriz<int, 5>);    // Ok.
cout << "El tipo es: " << ref.name() << endl;

Produce la siguiente salida:

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 más genéricas posibles conviene circunscribir al mínimo 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 quizás 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 definición 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 operación 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 explícita; en la segunda esta clase se transforma en una clase genérica, y en la tercera se incluye un segundo argumento que evita incluirlo en el constructor ( Ejemplo-4).

§8  Punteros y referencias a clases implícitas

Como hemos señalado, las clases implícitas gozan de todas las prerrogativas de las explícitas, incluyendo por supuesto la capacidad de definir punteros y referencias.  En este sentido no se diferencian en nada de aquellas. La única precaución es tener presente la cuestión de los tipos a la hora de efectuar asignaciones y no perder de vista que la plantilla es una abstracción que representa múltiples clases (tipos), cada una representada por argumentos concretos.

Consideremos la clase genérica Matriz ya utilizada anteriormente ( Ejemplo-4) cuya declaración es:

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

La definición de punteros y referencias sería como sigue:

Matriz<int,5> m1;                 // Ok. Tres objetos (matrices)
Matriz<char,5> m2;                // de tipos
Matriz<char> m3;                  // distintos.
...
Matriz<int,5>* ptrMi5 = &m2       // Error!! tipos distintos
Matriz<char,5>* ptrMch5 = &m2;    // Ok.
Matriz<char,1>* ptrMch1 = &m2;    // Error!! tipos distintos
Matriz<char,1>* ptrMch1 = &m3;    // Ok.
ptrMch5->show();                  // Ok. invocación de método mediante puntero
Matriz<char> m4 = *ptrMch1;       // L10: Ok asignación mediante puntero

Matriz<char>* mPtr = new Matriz<char>;  // Ok. puntero a objeto persistente

void (Matriz<char,5>::* fptr1)(); // L11: Ok. declara puntero a método
fptr1 = &Matriz<char,5>::show;    // Ok. asignación
(m3.*fptr1)();                    // Error!! tipo de m3 incorrecto
(m2.*fptr1)();                    // Ok. invocación de método deferenciando su puntero
 
Matriz<char,5>& refMch5 = m2;     // Ok. referencia
Matriz<char>& refMch1 = m4;       // Ok.
refMch5.show();                   // Ok. invocación de método mediante referencia al objeto

Comentario

Observe que la asignación L10 exige que la clase genérica Matriz tenga definido el constructor-copia y que esté sobrecargado el operador de indirección *  para los miembros de la clase ( 4.9.11).

Merecen especial atención las sentencias L11/L14. En este caso se trata de punteros-a-miembros de clases implícitas. El tipo de clase está definido en los parámetros. Observe que fptr1 es puntero-a-método-de-clase-Matriz<char, 5> [2], y no puede referenciar un método 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 capítulos: Punteros a Clases ( 4.2.1f) y Punteros a Miembros ( 4.2.1g).

§8.2  Ejemplo-5

En este ejemplo se muestra la utilización de punteros a clases implícitas, así como de la sobrecarga del operador de indirección ( Ejemplo-5).

  Inicio.


[1]  Esto es solo una generalización de un principio universal de programación: realizar pruebas con casos lo más simples posibles, e ir complicándolos paulatinamente.

[2]  Más formalmente: puntero-a-método que no acepta argumentos y devuelve void de la clase Matriz<char, 5>.

[3]  Suponiendo naturalmente que esta mayor "rigidez" no sea justamente el efecto que se pretende al definir nuevos argumentos.

[4]  Esta notación es análoga a la utilizada con las funciones genéricas que hemos denominado instanciación implícita específica ( 4.12.1)

[5]. En este sentido, no son como las funciones genéricas, que pueden ser declaradas antes de su utilización y definidas en cualquier otro sitio (incluso después de su uso -en el código-).

[6]  El ejemplo está tomado de un artículo de Zeeshan Amjad en "The Code Project".