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.2a  Clases genéricas II

§1  Las clases genéricas y la organización del código

Para la organización de las plantillas de clases genéricas pueden utilizarse las mismas pautas indicada para las funciones genéricas ( 4.12.1b):

  • Fichero único .

  • Fichero combinado .

  • Ficheros múltiples (compilación separada) .


§1.1
  El método de fichero único exige que el compilador encuentre la definición de las plantillas antes de su uso en cada unidad de compilación. Presenta el inconveniente de que las plantillas definidas en un módulo no son visibles desde los demás. Responde al siguiente esquema:

template<T> class C1 {  // declaración/definición
     ...   // definicion...
};
...
 
int main() {
  ...
  C1<char> c1;    // uso de una instancia
  ...
}


§1.2  El método de fichero combinado consiste en situar en un fichero de cabecera las definiciones de todas las clases genéricas (y de sus funciones-miembro). Posteriormente este fichero es incluido en todos los módulos que utilicen la clase genérica. La situación queda esquematizada como sigue:

// cabecera.h

 

#ifndef PLT_1

#define PLT_1

  template<T> class C1 { // declaración

     ...   // definicion...

  };

#endif     // PLT_1
...

// main.cpp

#include <cabecera.h>

...

int main() {

  ...

  C1<char> c1;

  ...

}

// modulo-1.cpp

#include <cabecera.h>

...

{

  ...

  C1<char*> ch;

  ...

}

// modulo-2.cpp

#include <cabecera.h>

...

{

  ...

  C1<X> cx;

  ...

}

De esta forma se garantiza que la plantilla pueda ser utilizada por más de una unidad de compilación.


§1.3  El método de compilación separada consiste en incluir en un fichero de cabecera las declaraciones de las clases genéricas, e incluir este fichero en todos los módulos de compilación mediante la oportuna directiva #include. Las definiciones off-line de todos los métodos van precedidas de la directiva export, y se incluyen en un fichero que es compilado separadamente:

// cabecera.h

 

#ifndef C_1

#define C_1

  template<T> class C1 { // declaración

    void C1<T>::f1();

    void C1<T>::f2();

    ...

  };

#endif     // C_1
...

// definiciones.cpp

#include <cabecera.h>

...

export template<class T> void C1<T>::f1() {

  ...   // definición 1

}

export template<class T> void C1<T>::f2() {

  ...   // definición 2

}
...

// modulo-1.cpp

#include <cabecera.h>

...

{

  ...

  C1<int> n1;

  ...

}
...

// modulo-2.cpp

#include <cabecera.h>

...

{

  ...

  C1<X> cx;

  ...

}
...

Desafortunadamente, a la fecha ninguno de los compiladores que hemos utilizado para plataformas PC incluye soporte para el especificador export en estas condiciones [1].

§2  El especificador friend con funciones genéricas

Recordemos que friend es un especificador de acceso, utilizado para declarar que ciertas funciones o clases (que denominamos "invitadas") tienen acceso al ámbito de ciertas clases "anfitrionas"  ( 4.11.2a1).  En el capítulo correspondiente se explicaron sus características y las indicaciones pertinentes sobre su uso. Sin embargo, cuando las funciones friend de una clase son a su vez funciones genéricas ("templates"), su utilización requiere algunas cauciones particulares.

Para empezar, debemos recordar que los "invitados" (friend) de una clase ordinaria o genérica (plantilla), pueden ser: una función, o una clase ordinarias (no genéricas); una función genérica ("template); una clase genérica; la especialización de una función genérica, y la especialización de una clase genérica [2].

Recordemos también, que cuando la clase anfitriona es una clase genérica (plantilla), sus propiedades y métodos son a su vez clases genéricas ( 4.12.2).  Como es usual, en estos casos la declaración de tales métodos debe hacerse en el cuerpo de la clase genérica, mientras que la definición puede hacerse dentro (on-line) o fuera (off-line), a continuación de la anterior.  Es el caso esquematizado a continuación, en el que suponemos una clase Array destinada a almacenar matrices unidimensionales de elementos de tipo numérico (int, float, long, float*, etc).

template <class T> class Array {     // clase genérica
   public:
   Array();                          // constructor por defecto
   Array& operator= (const Array&);  // operador de asignación
   ...
};

template<class T> Array<T>::Array()  // definicion del constructor
    /* alguna función de T ... */
}

template<class T> Array<T>& Array<T>::operator= (const Array<T>& ar) {
    /* alguna función de T ... */
    ...

    return *this;
}

En este ejemplo, tanto el constructor por defecto como el operador de asignación, se han definido off-line a continuación del cuerpo de la clase; exactamente igual que en una clase ordinaria, aunque teniendo en cuenta que en esta ocasión, ambos métodos son a su vez funciones genéricas. En realidad, cualquiera que sea la forma de su definición (on-line u off-line), ambos métodos están en el ámbito de la clase y pertenecen a ella. Cuando se obtiene una especialización de la clase genérica. Por ejemplo:

Array <float> a1;

el compilador conoce el tipo de argumento <T> que deberá utilizar para construir la especialización de cada método de la clase.


Por su parte, la declaración de funciones friend genéricas, debe hacerse en el cuerpo de la clase anfitriona, pero la definición debe hacerse antes del cuerpo de dicha clase.  A su vez, los prototipos situados en el cuerpo de la clase, además del especificador friend, deben incluir un indicador del tipo de especialización que se pretende, mediante el indicador <T> situado entre el nombre de la función y la lista de argumentos (lo hemos denominado instanciación explícita específica    4.12.1). Es el caso esquematizado a continuación relativo a la clase anterior, en el que añadimos dos funciones amigas, que son a su vez funciones genéricas.

template<class T> Array<T> operator+ (const Array<T>& ar1, const Array<T>& ar2) {
   // versión sobrecargada de operator+() para concatenar rrays
   Array<T> ax(len1 + len2);    // Array auxiliar para el resultado
   ...
   return ax;
}

template<class T> std::ostream& operator<< (std::ostream& stream, const Array<T> arr) {
   // versión sobrecargada de operador<<() para salida estándar
   ...
   return stream;
}

template <class T> class Array {     // clase genérica
   friend Array<T> operator+ <T> (const Array<T>&, const Array<T>&);    // L1.
   friend std::ostream& operator<< <T> (std::ostream&, const Array<T>); // L.2

   public:
   Array();                          // constructor por defecto
   Array& operator= (const Array&);  // operador de asignación
   ...
};

...

La sentencia L1 declara el operador suma para los objetos de la clase. Observe que de las dos opciones disponibles para este operador, se ha elegido la de función externa con dos argumentos ( 4.9.18); la otra alternativa nos habría conducido a una función-miembro con un argumento, con lo que estaríamos en el supuesto del ejemplo anterior .  Por su parte, L2 es una versión sobrecargada de la función operator<< , definida como una función externa, que facilitará la salida para objetos tipo Array

Aunque los prototipos de ambas funciones-operador están en el cuerpo de la clase (para señalar que son funciones invitadas), en realidad ninguna de ellas pertenece al ámbito de la clase (en este caso pertenecen al ámbito global del fichero). La consecuencia es que, aunque sus definiciones contengan referencias a un tipo T, el compilador no puede deducir que el tipo utilizado para instanciar estas funciones sea justamente el utilizado en una especialización concreta de la clase (piense que estas funciones pueden ser utilizadas desde espacios distintos de la clase Array).  Por esta razón hay que recurrir a la instanciación explícita específica; para que el compilador pueda conocer el tipo de argumento que se utilizará para instanciar la función genérica correspondiente a cada especialización de la clase Array

  Inicio.


[1]  Son las siguientes:  Borland C++ 5.5;  MS Visual C++ 6.0;  GNU cpp 2.95.2

[2]  ISO/IEC 14882 - 1ª Edición §14.5.3 (pag. 274).