40 Plantillas

Según va aumentando la complejidad de nuestros programas y sobre todo, de los problemas a los que nos enfrentamos, descubrimos que tenemos que repetir una y otra vez las mismas estructuras.

Por ejemplo, a menudo tendremos que implementar arrays dinámicos para diferentes tipos de objetos, o listas dinámicas, pilas, colas, árboles, etc.

El código es similar siempre, pero estamos obligados a rescribir ciertas funciones que dependen del tipo o de la clase del objeto que se almacena.

Las plantillas (templates) nos permiten parametrizar estas clases para adaptarlas a cualquier tipo de dato.

Vamos a desarrollar un ejemplo sencillo de un array que pueda almacenar cualquier objeto. Aunque más adelante veremos que se presentan algunas limitaciones y veremos cómo solucionarlas.

Con lo que ya sabemos, podemos crear fácilmente una clase que encapsule un array de, por ejemplo, enteros. Veamos el código de esta clase:

// TablaInt.cpp: Clase para crear Tablas de enteros
// C con Clase: Marzo de 2002

#include <iostream>
using namespace std;

class TablaInt {
  public:
   TablaInt(int nElem);
   ~TablaInt();
   int& operator[](int indice) { return pInt[indice]; }

  private:
   int *pInt;
   int nElementos;
};

// Definición:
TablaInt::TablaInt(int nElem) : nElementos(nElem) {
   pInt = new int[nElementos];
}

TablaInt::~TablaInt() {
   delete[] pInt;
}

int main() {
   TablaInt TablaI(10);

   for(int i = 0; i < 10; i++)
      TablaI[i] = 10-i;

   for(int i = 0; i < 10; i++)
      cout << TablaI[i] << endl;

   return 0;
}

Bien, la clase TablaInt nos permite crear arrays de la dimensión que queramos, para almacenar enteros. Quizás pienses que para eso no hace falta una clase, ya que podríamos haber declarado sencillamente:

   int TablaI[10];

Bueno, tal vez tengas razón, pero para empezar, esto es un ejemplo sencillo. Además, la clase TablaInt nos permite hacer cosas como esta:

   int elementos = 24;
   TablaInt TablaI(elementos);

Recordarás que no está permitido usar variables para indicar el tamaño de un array. Pero no sólo eso, en realidad esta podría ser una primera aproximación a una clase TablaInt que nos permitiría aumentar el número elementos o disminuirlo durante la ejecución, definir constructores copia, o sobrecargar operadores suma, resta, etc.

La clase para Tabla podría ser mucho más potente de lo que puede ser un array normal, pero dejaremos eso para otra ocasión.

Supongamos que ya tenemos esa maravillosa clase definida para enteros. ¿Qué pasa si ahora necesitamos definir esa clase para números en coma flotante?. Podemos cortar y pegar la definición y sustituir todas las referencias a int por float. Pero, ¿y si también necesitamos esta estructura para cadenas, complejos, o para la clase persona que implementamos en anteriores capítulos?, ¿haremos una versión para cada tipo para el que necesitemos una Tabla de estas características?.

Afortunadamente existen las plantillas y (aunque al principio no lo parezca), esto nos hace la vida más fácil.

Sintaxis

C++ permite crear plantillas de funciones y plantillas de clases.

La sintaxis para declarar una plantilla de función es parecida a la de cualquier otra función, pero se añade al principio una presentación de la clase que se usará como referencia en la plantilla:

template <class|typename <id>[,...]>
<tipo_retorno> <identificador>(<lista_de_parámetros>)
{
   // Declaración de función
};

La sintaxis para declarar una plantilla de clase es parecida a la de cualquier otra clase, pero se añade al principio una presentación de la clase que se usará como referencia en la plantilla:

template <class|typename <id>[,...]>
class <identificador_de_plantilla>
{
   // Declaración de funciones 
   // y datos miembro de la plantilla
};

Nota: La lista de clases que se incluye a continuación de la palabra reservada template se escriben entre las llaves "<" y ">", en este caso esos símbolos no indican que se debe introducir un literal, sino que deben escribirse, no me es posible mostrar estos símbolos en negrita, por eso los escribo en rojo. Siento si esto causa algún malentendido.

Pero seguro que se ve mejor con un ejemplo:

template <class T1>
class Tabla {
  public:
   Tabla();
   ~Tabla();
   ...
};

Del mismo modo, cuando tengamos que definir una función miembro fuera de la declaración de la clase, tendremos que incluir la parte del template y como nombre de la clase incluir la plantilla antes del operador de ámbito (::). Por ejemplo:

template <class T1>
Tabla<T1>::Tabla() {
  // Definición del constructor
}

Plantillas de funciones

Un ejemplo de plantilla de función puede ser esta que sustituye a la versión macro de max:

template <class T> 
T max(T x, T y) {
   return (x > y) ? x : y;
};

La ventaja de la versión en plantilla sobre la versión macro se manifiesta en cuanto a la seguridad de tipos. Por supuesto, podemos usar argumentos de cualquier tipo, no han de ser necesariamente clases. Pero cosas como estas darán error de compilación:

int a=2;
char b='j';

int c=max(a,b);

El motivo es que a y b no son del mismo tipo. Aquí no hay promoción implícita de tipos. Sin embargo, están permitidas todas las combinaciones en las que los argumentos sean del mismo tipo o clase, siempre y cuando que el operador > esté implementado para esa clase.

int a=3, b=5, c;
char f='a', g='k', h;
c = max(a,b);
h = max(f,g);

Plantilla para Tabla

Ahora estamos en disposición de crear una plantilla a partir de la clase Tabla que hemos definido antes. Esta vez podremos usar esa plantilla para definir Tablas de cualquier tipo de objeto.

template <class T>
class Tabla {
  public:
   Tabla(int nElem);
   ~Tabla();
   T& operator[](int indice) { return pT[indice]; }

  private:
   T *pT;
   int nElementos;
};

// Definición:
template <class T>
Tabla<T>::Tabla(int nElem) : nElementos(nElem) {
   pT = new T[nElementos];
}

template <class T>
Tabla<T>::~Tabla() {
   delete[] pT;
}

Dentro de la declaración y definición de la plantilla, podremos usar los parámetros que hemos especificado en la lista de parámetros del template como si se tratase de comodines. Más adelante, cuando creemos instancias de la plantilla para diferentes tipos, el compilador sustituirá esos comodines por los tipos que especifiquemos.

Y ya sólo nos queda por saber cómo declarar Tablas del tipo que queramos. La sintaxis es:

<identificador_de_plantilla><<tipo/clase>> <identificador/constructor>;

Seguro que se ve mejor con un ejemplo, veamos como declarar Tablas de enteros, punto flotante o valores booleanos:

Tabla<int>   TablaInt(32);   // Tabla de 32 enteros
Tabla<float> TablaFloat(12); // Tabla de 12 floats
Tabla<bool>  TablaBool(10);  // Tabla de 10 bools

Pero no es este el único modo de proceder. Las plantillas admiten varios parámetros, de modo que también podríamos haber especificado el número de elementos como un segundo parámetro de la plantilla:

template <class T, int nElementos>
class Tabla {
  public:
   Tabla();
   ~Tabla();
   T& operator[](int indice) { return pT[indice]; }

  private:
   T *pT;
};

// Definición:
template <class T, int nElementos>
Tabla<T,nElementos>::Tabla() {
   pT = new T[nElementos];
}

template <class T, int nElementos>
Tabla<T, nElementos>::~Tabla() {
   delete[] pT;
}

La declaración de tablas con esta versión difiere ligeramente:

Tabla<int,32>   TablaInt;   // Tabla de 32 enteros
Tabla<float,12> TablaFloat; // Tabla de 12 floats
Tabla<bool,10>  TablaBool;  // Tabla de 10 bools

Esta forma tiene una limitación: el argumento nElementos debe ser una constante, nunca una variable. Esto es porque el valor debe conocerse durante la compilación del programa. Las plantillas no son definiciones de clases, sino plantillas que se usan para generar las clases que a su vez se compilarán para crear el programa:

#define N 12
...
const n = 10;
int i = 23;

Tabla<int,N>     TablaInt;   // Legal, Tabla de 12 enteros
Tabla<float,3*n> TablaFloat; // Legal, Tabla de 30 floats
Tabla<char,i>    TablaFloat; // Ilegal

Comentarios de los usuarios (23)

GHOST
2011-11-08 23:27:30

Tengo un problema con el ejemplo que usa como cabecera el archivo CCadena.h para las Tablas de cadenas, lo probé en codeBlocks y Dev-c++, y me da más de 15 errores, creo que se deben al uso del puntero this, pero no se si estoy en lo correcto, por favor, alguien que pueda aclarar mi duda se lo agradecería mucho.

Steven R. Davidson
2011-11-09 13:00:28

Hola Ghost,

El único error que he visto es que en "CCadena.h", usamos 'ostream' sin #incluir <iostream> ni tampoco escribimos el ámbito 'std'; esto es,

#include <cstring>
#include <iostream>

using std::strcpy;
using std::strlen;
using std::ostream;
...

Si hay otros errores, sería mejor que nos mostraras los mensajes que te lanza el compilador.

Espero que esto te sirva.

Steven

GHOST
2011-11-09 20:29:53

Gracias Steve tienes razon esos son los errores, gracias, ya que este es el mejor curso de C++ de la red.

Neylor
2011-12-09 12:28:23

He intentado crear un proyecto con las declaraciones de las plantillas en un fichero .h y las definiciones en un fichero .cpp y al usarlas en el main (fichero .cpp aparte del de las definiciones de las plantillas) me da errores del tipo "unresolved external symbol". No quiero integrarlo todo en un unico fichero, puesto que es bastante codigo. ¿Alguien tiene alguna idea de por qué ocurre esto? He declarado el correspondiente #include del fichero .h tanto en el fichero de definiciones como en el main. Un saludo y gracias.

Steven R. Davidson
2011-12-09 13:24:13

Hola Neylor,

El problema es que estás separando la definición de la clase-plantilla (.h) de la implementación de sus funciones miembro (.cpp). Las plantillas son un mecanismo para el compilador, ya que éste es quien genera las clases a partir de las plantillas. Por lo tanto, deberías hacer esto en tu caso,

// "fichero.h"

template< typename T >
class Algo
{
  ...
};

#include "fichero.cpp"  // sin que incluya a "fichero.h"

De esta manera, al incluir este fichero de cabecera en "main.cpp", el compilador tendrá acceso no solamente a la definición de la plantilla pero también a la implementación de sus miembros.

La otra solución es copiar TODO lo que hay en "fichero.cpp" y pegarlo íntegramente en "fichero.h" después de tales definiciones de las plantillas.

Espero haber aclarado la duda.

Steven

Neylor
2011-12-10 12:13:16

¡Muchas gracias Steven!

LLevaba mas de dos días atascado con eso debido a que estaba usando una metodología incorrecta, que con las clases normales si me funcionaba. Me gustaría añadir por si alguna vez alguien se encuentra con este problema, es que la solución es la que has proporcionado, con el añadido para los que usemos un entorno de desarollo integrado como yo, que ademas se tiene que quitar el fichero .cpp de la definicion de los metodos de la clase del proyecto, de lo contrario hay problemas de redefinicion.

logoff
2012-02-02 14:14:10

¿Se podría crear una clase con template y que el genérico esté limitado? No sé si me explico, os pongo un ejemplo:

template<class T> class ClaseConClase {
public:
// lo que sea
private
// lo que sea, pero privado
};

Donde T esté limitado, por ejemplo con herencia. Vamos, que T sea subclase de otra clase.

emiliano
2012-05-26 06:35:58

Hola ,por favor necesito que alguien me ayude !! estoy haciendo trabajo ,un TDA lista con templates soy nuevo en esto ,ya hize el codigo pero no me deja ejecutar porque me aparece este cartel:

ld.exe||cannot open output file bin\Debug\lista.exe Permission denied|

||=== Build finished: 1 errors, 0 warnings ===|

Alguien sabe como solucionarlo?gracias!!!

emiliano
2012-05-26 06:51:55

este es el codigo del cual hablo,lo que esta comentado no lo hice todavia :

#ifndef LISTA_H_INCLUDED

#define LISTA_H_INCLUDED

#include <iostream>

using namespace std;

template <class ELEMENT>

class lista {

private:

struct nodo {

ELEMENT elemento;

nodo* sig;

};

nodo *head;

nodo *pointer;

int Cant_Elemen;

void AgregarInicio( ELEMENT elemento);

void AgregarFinal( ELEMENT elemento,nodo * & pointer);

void destruir_estructura(nodo* & head);

void reset();

public :

lista();

~lista();

void inicioLista();

void AgregarElemento( ELEMENT elemento);

// int LongLista();

// void AgregarEposicion(int elem,int posicion)

void mostrarlista(nodo *puntero);

/* bool Pertenece(int elem);

bool EsVacia();

void eliminar(int elem);

void mostrarlista(lista *puntero);

*/

};

//////////////CONSTRUCTOR DE LA CLASE

template <class ELEMENT>

lista< ELEMENT >::lista(){

head=0;

Cant_Elemen=0;

};

//////////////INICIA LA CLASE LISTA

template<class ELEMENT >

void lista<ELEMENT>::inicioLista(){

pointer=head;

};

//-------------Agrega un elemento al principio de la lista

template<class ELEMENT >

void lista< ELEMENT >::AgregarInicio( ELEMENT elemento)

{

nodo *aux =new nodo;

aux->elemento=elemento;

aux->sig= head;

head=aux;

Cant_Elemen++;

};

//------Agrega un elemento al final de la lista

template<class ELEMENT >

void lista<ELEMENT> ::AgregarFinal(ELEMENT elemento, nodo * & pointer)

{

pointer= new nodo;

pointer->elemento = elemento;

pointer->sig = NULL;

};

template<class ELEMENT >

void lista< ELEMENT>::AgregarElemento(ELEMENT elemento){

AgregarInicio(elemento);

pointer=head;

while (pointer!=0)

pointer=pointer->sig;

AgregarFinal(elemento,pointer);

};

//--------Muestra la Lista

template<class ELEMENT >

void lista< ELEMENT >::mostrarlista(nodo *puntero){

if (puntero!= NULL)

{

cout << puntero->valor << endl;

mostrarlista(puntero->sig);

}

}

template <class ELEMENT>

void lista<ELEMENT>::destruir_estructura(nodo * & head)

{

if (head){

destruir_estructura(head->sig);

delete head;

}

}

template <class ELEMENT>

void lista<ELEMENT>::reset()

{

destruir_estructura(head);

head=0;

pointer=0;

Cant_Elemen=0;

}

template <class ELEMENT>

lista<ELEMENT>::~lista()

{

reset();

};

/*

//--------------------------------------------

void vaciarlista(lista *&punt_lista)

{

if(punt_lista != NULL)

{

vaciarlista(punt_lista->sig);

delete punt_lista;

punt_lista = NULL;

}

}

//--------------------------------------------

void eliminanodo(int valor, lista *&punt_lista)

{

if(punt_lista != NULL)

{

if (punt_lista->valor == valor)

{

lista* aux = punt_lista;

punt_lista = punt_lista->sig;

delete aux;

}

else

eliminanodo(valor, punt_lista->sig);

}

}

//--------------------------------------------

*/

#endif // LISTA_H_INCLUDED

Steven R. Davidson
2012-05-27 08:26:56

Hola Emiliano,

El error es del enlazador (linker, en inglés), "ld.exe". Posiblemente, estés ejecutando el programa, "lista.exe", sin darte cuenta. Detén el proceso, a través de tu IDE o manualmente a través del sistema operativo.

Si aún así no funciona, entonces manualmente elimina el fichero "lista.exe" en su directorio. Quizá también sea prudente borrar el proyecto de tu IDE y recrearlo. Ten cuidado de no borrar los ficheros fuente y de cabecera.

Espero que esto te sirva.

Steven

Rod
2013-03-22 03:54:26

Hola Steven hace poco descubrí tu sitio y debo decir que me ha ayudado mucho. Necesito ayuda o consejos con respecto a las plantillas de un arreglo dinámico ya que tengo una tarea que las necesita básicamente tengo un código y debo pasarlo a plantilla te voy a mostrar lo que tengo pero me da errores que no entiendo.

El programa consiste en recibir números e imprimirlos al inverso del orden en el que fueron ingresados (del ultimo al primero)

#include <iostream>
#include <assert.h> //para poder usar assert()
using namespace std;


template <class Array> 
class Arreglo {
public:
	template <class Array>
	Arreglo<Array>::Arreglo(int size) : size(size) {
		data = new Array[size];
		cap = size;
		used = 0;
	}
	template <class Array>
	Arreglo<Array>::~Arreglo() {
		delete []data;
	}

	void AddValor(int value) {
		if (cap == used) {
			int *new_data = new int[cap * 2];
			for (int i = 0; i < cap; i++) {
				new_data[i] = data[i];
			}
			new_data[used] = value;
			delete [] data;
			data = new_data;
			cap *= 2;
		}
		data[used] = value;
		used ++;
	}

	void SetValor(int pos, int value) {
		data [pos] = value;
	}

	int GetValor(int pos) {
		return data[pos];
	}

	int GetSize() {
		return used;
	}

protected:
	Array *data;
	int cap, used;
};

int main(int argc, char **argv) {
	Arreglo array(1);
	int value;
	
	while ((cin >> value)) {
		array.AddValue(value);
	}

	for(int i = array.GetSize() - 1; i >= 0; i--)
		cout << array.GetValue(i) << endl;

	return 0;
}

Me da los siguientes errores (estoy en linux)

arreglo_plantilla.cpp:9:12: error: la declaración de ‘class Array’

arreglo_plantilla.cpp:6:11: error: oscurece el parámetro de plantilla ‘class Array’

arreglo_plantilla.cpp:10:34: error: uso inválido del tipo incompleto ‘class MyArray<Array>’

arreglo_plantilla.cpp:7:7: error: la declaración de ‘class MyArray<Array>’

arreglo_plantilla.cpp:15:12: error: la declaración de ‘class Array’

arreglo_plantilla.cpp:6:11: error: oscurece el parámetro de plantilla ‘class Array’

arreglo_plantilla.cpp:16:27: error: uso inválido del tipo incompleto ‘class MyArray<Array>’

arreglo_plantilla.cpp:7:7: error: la declaración de ‘class MyArray<Array>’

arreglo_plantilla.cpp: En la función ‘int main(int, char**)’:

arreglo_plantilla.cpp:53:10: error: missing template arguments before ‘array’

arreglo_plantilla.cpp:53:10: error: expected ‘;’ before ‘array’

arreglo_plantilla.cpp:57:3: error: ‘array’ no se declaró en este ámbito

arreglo_plantilla.cpp:60:14: error: ‘array’ no se declaró en este ámbito

De antemano muchas gracias!

Steven R. Davidson
2013-03-22 07:07:36

Hola Rod,

Los primeros errores tienen que ver con el hecho de que has definido el parámetro 'Array' de la plantilla 'Arreglo()' dentro de la definición de la plantilla, 'Arreglo', cuyo parámetro es 'Array'. Es decir, ahora hay dos plantillas con dos parámetros del mismo nombre de 'Array'.

Lo que seguramente querías hacer es una sola clase-plantilla:

template< typename Array >
class Arreglo
{
public:
  Arreglo( int size )  // He quitado lo de: size(size)
  {...}
  ~Arreglo()
  {
    delete[] data;
  }
  ...
};

Tendrías que reescribir lo de 'template', si definieras las funciones miembro fuera de la definición de la clase-plantilla. Por ejemplo,

template< typename Array >
class Arreglo
{
public:
  Arreglo( int size );
  ~Arreglo();
  ...
};

template< typename Array >
Arreglo<Array>::Arreglo( int size )
{
  ...
}

template< typename Array >
Arreglo<Array>::~Arreglo()
{
  delete[] data;
}

El otro error es en 'main()', cuando escribes:

Arreglo array(1);

Recuerda que 'Arreglo' es una plantilla y no una clase. Para generar una clase, tenemos que dar un parámetro para la plantilla. Por ejemplo,

Arreglo<int> array(1);

Espero que esto te aclare las dudas.

Steven

rod
2013-03-22 13:13:11

A ya veo, al parecer me complique más de lo que debia. Definitivamente queria hacer lo de una sola plantilla para toda la clase ahora el código lo arregle como me dijiste y me quedó así (cambié Arreglo por MyArray para poder diferenciarlo más rápido del nombre del template):

#include <iostream>
#include <assert.h> //para poder usar assert()
using namespace std;


template <class Array> 
class MyArray {
public:
	MyArray(int size){
		data_ = new Array[size];
		cap_ = size;
		used_ = 0;
	}
	~MyArray() {
		delete []data_;
	}

	void AddValue(int value) {
		if (cap_ == used_) {
			int *new_data = new int[cap_ * 2];
			for (int i = 0; i < cap_; i++) {
				new_data[i] = data_[i];
			}
			new_data[used_] = value;
			delete [] data_;
			data_ = new_data;
			cap_ *= 2;
		}
		data_[used_] = value;
		used_++;
	}

	void SetValue(int pos, int value) {
		data_[pos] = value;
	}

	int GetValue(int pos) {
		return data_[pos];
	}

	int GetSize() {
		return used_;
	}

protected:
	Array *data_;
	int cap_, used_;
};

int main(int argc, char **argv) {
	MyArray<int> array(1);
	int value;
	
	while ((cin >> value)) {
		array.AddValue(value);
	}

	for(int i = array.GetSize() - 1; i >= 0; i--)
		cout << array.GetValue(i) << endl;

	return 0;
}


Aunque me queda una duda, supuse que iba a tener que usar la funcion assert para evitar errores, estoy en lo correcto o no es necesaria?

Ahora si compila sin errores y se ejecuta perfectamente. Gracias!

Steven R. Davidson
2013-03-22 19:40:35

Hola Rod,

No aconsejo usar 'assert()' para la versión final del programa; está bien para la fase de depuración. Para controlar errores, programa la verificación de los datos y la resolución de los errores en cada función que requiera tal tarea. Si se trata de errores muy "raros" que ocurren en circunstancias excepcionales, entonces puedes implementar el sistema de excepciones que tratamos en el capítulo 43: http://c.conclase.net/curso/index.php?cap=043#inicio

Espero que esto te oriente.

Steven

jose
2014-02-10 21:58:01

Estoy tratando de hacer un programa en c++ con funciones que imprima el numero 10 diez veces, pero no me compila me sale un error "cannot return a value"

este es el codigo

#include <iostream.h>

#include <stdio.h>

void leer(int)

{

int i;

for(i=0;i<10;i++)

return 0;

}

void imprimir(int) {

int j=10;

printf("%d", j);

}

agradesco mucho su ayuda

ivanhdzd
2014-05-01 23:52:33

Una duda sobre plantillas:

Me envía un error al tratar de declarar mis clases con plantillas (Apenas estoy aprendiendo a usarlas):

#ifndef NodoH
#define NodoH

template< class T >
class Nodo {
    friend class Pila;
private:
    T valor;
    Nodo *ptrSig;
public:
    Nodo( T val, Nodo *Sig = NULL )
    : valor( val ) { ptrSig = Sig; }
};

#endif // NodoH

template< class T >
typedef Nodo< T > *ptrNodo< T >;

#ifndef PilaH
#define PilaH

template< class T >
class Pila {
private:
    Nodo< T > *ptrInicio;
    Nodo< T > *ptrCima;
public:
    ;
};

#endif // PilaH

Me marca error en:

typedef Nodo< T > *ptrNodo< T >;

donde me dice error: template declaration of 'typedef'

Y la verdad no tengo idea si se declare así mi tipo de dato.

Steven R. Davidson
2014-05-03 19:35:30

Hola Iván,

No puedes crear una plantilla de un tipo de dato, pero sí puedes crear un tipo de dato dentro de una plantilla de una clase, que es lo que recomendamos hacer. Por ejemplo,

template< typename T >
class Nodo
{
public:
  typedef Nodo *ptrNodo;  // Miembro de cualquier clase: 'Nodo<T>'
...
};

Así podemos hacer esto:

template< typename T >
class Pila
{
private:
  typename Nodo< T >::ptrNodo ptrInicio;
  typename Nodo< T >::ptrNodo ptrCima;
...
};

Percátate del uso del vocablo 'typename' antes de usar 'ptrNodo', ya que el compilador no sabe exactamente el tipo de dato en estos momentos.

También tienes otro error al escribir:

template< typename T >
class Nodo
{
  friend class Pila<T>;
  ...
}

Esto implica que el compilador debe saber qué es 'Pila'. Para esto, usa una declaración anticipada (o adelantada); esto es,

template< typename T > class Pila;

template< typename T >
class Nodo
{
  friend class Pila<T>;
  ...
}

Espero que esto te aclare las dudas.

Steven

ivanhdzd
2014-09-11 21:59:11

Gracias Steven R. Davidson (tardé mucho en agradecerte) realmente me sirvió mucho tu respuesta.

La verdad ya había dejado a un lado las plantillas en C++

Pero últimamente me volvió a interesar y encontré tu respuesta.

Ricardo Francisco
2015-09-22 18:19:13

Una duda sobre las plantillas.

Supongamos que tenga una plantilla perfectamente operativa en un fichero que llamo "field.h". Para ilustrarla un poco tiene esta pinta.

template <class T> struct field{
    /* Vector Field*/
    int Nx,Ny,Nz,offset;
    T *array;
    field()    {
        Nx=Ny=Nz=offset=0;
        array=NULL;
    }
    field(int x,int y,int z,int ox,int oy,int oz){
        alloc(x,y,z,ox,oy,oz);
    }
    field(int x,int y,int z){
        alloc(x,y,z,1,1,1);
    }
    void alloc(int x,int y,int z){
        alloc(x,y,z,1,1,1);
    }
    void zalloc(int x,int y,int z){
        alloc(x,y,z,0,0,0);
    }
    
    void alloc (int x,int y,int z,int ox,int oy,int oz){
        Nx=x;
        Ny=y;
        Nz=z;
        offset=oz+(oy*Nz)+(ox*Ny*Nz);
	offset=-offset;
        array=new T[Nx*Ny*Nz];
    }
    
    T& operator()(int i,int j,int k)
    {return(array[k+(j*Nz)+(i*Ny*Nz)+offset]);}
    
    int address(int i,int j,int k)
    {return(k+(j*Nz)+(i*Ny*Nz)+offset);}
    
    ~field() {if (array!=NULL) delete [] array; array=NULL;}
};

y mi programa tiene unos cuantos ficheros , que incluyen "field.h" porque hacen uso de la plantilla , con los mismos tipos "T".

Por ejemplo:

main.cpp

calcular.cpp

tensor.cpp

field.cpp

Mi pregunta es:

si en main.cpp declaro y uso un field<double> y en calcular.cpp también declaro y uso un field<double>, ¿se está duplicando el código de la plantilla en los códigos objeto main.o y calcular.o ?

de la misma manera que si en C declaro una función dentro de un archivo de cabecera de la forma:

 static float funcion_alpha (float x,float y)
{ ... código ... }

y lo incluyo en varios ficheros.

Steven R. Davidson
2015-09-27 18:44:54

Hola Ricardo,

La respuesta es "sí". Cada fichero objeto contendrá el mismo código de las funciones miembro (de la estructura 'field<double>') como propio.

Ahora bien, durante el enlace (o ligadura) sí hay una diferencia importante entre las funciones miembro de la estructura, la cual se basa en la plantilla, y una función estática. A la hora de crear el ejecutable, el enlazador entiende que las funciones miembro de 'field<double>' son comunes y por tanto el ejecutable sólo contendrá una "copia" de sus definiciones. Este comportamiento es diferente al enlazar (o ligar) una función estática. El enlazador mantendrá la existencia de cada función estática, aunque existan varias "versiones" con el mismo nombre, en un mismo fichero ejecutable.

Espero haber aclarado la duda.

Steven

Ricardo Francisco
2015-09-28 11:41:07

Gracias Steven.

Ronald
2016-03-31 19:52:43

Disculpen mi pregunta es la siguiente. Si por ejemplo agrego una clase el cual tiene su .h y su .cpp, a la hora de crear un principal.cpp donde tengo el main, como hago para que no me de error, ya que al agregar el .h al principal.cpp donde tengo el main me da error. y si encuentran la solucion me podrian dar la explicacion.

Gracias!

Steven R. Davidson
2016-04-03 01:10:55

Hola Ronald,

Sin conocer el mensaje del error ni tampoco "quién" lo generó es algo difícil de dar una solución concreta.

Sospecho que el problema es que no has compilado el otro ".cpp", por lo que el enlazador (linker) te generará un error. Otro problema puede ser que sí compilaste el otro ".cpp", pero el enlazador lo desconoce.

La solución a ambas posibilidades es crear un proyecto y agregar todos los ".cpp" y ".h" a tal proyecto. En lugar de compilar, debes elegir "construir el proyecto" (o en inglés, "build project") o algo parecido.

Espero que esto te oriente.

Steven