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.1b  Funciones genéricas (continuación)

§1  Polimorfismo en funciones genéricas

En capítulos precedentes nos hemos referido a esta característica del lenguaje C++ que concierne tanto a funciones normales ( 4.4.1) como a métodos de clases ( 4.11.2a2). Es posible también la sobrecarga de funciones genéricas; incluso mezclando definiciones de plantillas del mismo nombre con funciones explícitas. 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 más tarde el compilador encuentra una invocación a la función modulo( ), deduce la versión a utilizar (o generar si corresponde a una plantilla), basándose en los argumentos utilizados, y en una generalización de las reglas de congruencia estándar de argumentos utilizadas en la sobrecarga de funciones ( 4.4.1a).

§2  Las plantillas y la organización del código

Para la organización de las plantillas en el código pueden seguir tres estrategias distintas:

  • Fichero único 
  • Fichero combinado 
  • Ficheros múltiples (compilación separada)

§2.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.

Su uso está recomendado cuando se utilizan plantillas en programas pequeños, con un solo fuente, o cuando existen varias unidades de compilación ( 1.4.2) pero las plantillas se utilizan solo en una de ellas, la organización del código suele ser la misma que con funciones explícitas, es decir: Primero se declaran, a continuación se utilizan y finalmente se definen según el siguiente esquema:

template <class T> void f1(const T&);  // Declaración
...
f1(t1);     // Uso
...
template <class T> void f1(const T& ref) { // Definición 
  ...
}

Las otras dos estrategias se aconsejan para programas más extensos, en los que existen varias unidades de compilación que referencian a las mismas plantillas


§2.2  El método de fichero combinado consiste en situar en un fichero de cabecera las definiciones de todas las funciones genéricas. Posteriormente este fichero es incluido en todos los módulos que utilicen la función.  La situación queda esquematizada como sigue:

// cabecera.h

 

#ifndef F_1

#define F_1

template<T> void f1(T t) { 

   /* definicion... */

};

#endif     // F_1

// main.cpp

#include <cabecera.h>

...

int main() {  // =====

  ...

  f1(a);

  ...

}

// modulo1.cpp

#include <cabecera.h>

...

{

  ...

  f1(X);

  ...

}

// modulo2.cpp

#include <cabecera.h>

...

{

  ...

  f1(Z);

  ...

}

De esta forma se garantiza que la plantilla pueda ser utilizada por más de una unidad de compilación, y se delega en el compilador la tarea de generar el código adecuado y eliminar las definiciones redundantes. Observe que es justamente la misma estrategia que con las funciones explícitas definidas por el usuario ( 4.4.1). El inconveniente es que la declaración está presente en todos los módulos, con la consiguiente sobrecarga para el compilador que debe procesarla.


§2.3
  El método de compilación separada consiste en incluir una declaración en cada módulo antes de su uso, e incluir la definición en un fichero que es compilado separadamente. En este caso, la disposición de ficheros queda como sigue:

// cabecera.h

 

#ifndef F_1

#define F_1

template<T> void f1(T); // declaración

  ...   // otras declaraciones

#endif    // F_1

// definiciones.cpp

#include <cabecera.h>

 

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

  ...     // definición

}

...       // otras definiciones

// modulo1.cpp

#include <cabecera.h>

...

{

  ...

  f1(X);  // usar función

  ...

}

// modulo2.cpp

#include <cabecera.h>

...

{

  ...

  f1(Z);  // usar función

  ...

}

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 también las definiciones de otras funciones del proyecto), es compilado en una unidad de compilación 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 definición como en el caso anterior, pero a cambio debe localizar la única instancia de la definición cada vez que sea necesaria. Observe que se necesita la declaración export para que la definición sea accesible desde las otras unidades de compilación.


§3 
export es una palabra clave [1] opcional que indica al compilador que la declaración será accesible desde otras unidades de compilación, y solo puede ser utilizada una vez con cada declaración (Recuerde el principio de una sola definición 4.1.2).

Nota: en la página adjunta se incluyen algunas instrucciones para el compilador Borland ( BC++ 55 compiler switches).

§4  Tipos locales como argumentos de plantillas

Tanto las funciones genéricas como las clases genéricas se instancian una sola vez para cada tipo. El compilador utiliza un sistema de discriminación basado en la equivalencia nombre-tipo, utilizado en cada caso como parámetro de la plantilla, para decidir que plantillas deben ser instanciadas o re-instanciadas.

Por ejemplo, considere los siguientes módulos en el modelo de compilación separada :

// modulo1.cpp

#include <cabecera.h>

...

{

  class X { int data; };

  ...

  f1(X);  // usar función 

  ...

}

// modulo2.cpp

#include <cabecera.h>

...

{

  class X {float data; };

  ...

  f1(X);  // usar función

  ...

}

En estos casos pueden ocurrir errores serios, dado que el tipo de X en el primer módulo no coincide con el tipo de X en el segundo. Sin embargo, la comprobación nombre-tipo los hace parecer como iguales. El corolario es que debe intentarse que la instanciación implícita de funciones (o clases) genéricas no dependa de tipos locales.

  Inicio.


[1]  A la hora de redactar estas líneas, ni el compilador Borland C++ 5.5 ni MS Visual C++ 6.0, ni GNU g++  incluyen soporte para esta palabra clave sobre cuya inclusión en el Estándar ha existido gran controversia.  En el enlace adjunto pueden verse algunas opiniones al respecto "Serious Promises and Standard C++"   www.comeaucomputing.com.