c++ - sobrecarga - "Referencia indefinida para" constructor de clase de plantilla
que es un destructor en c++ (3)
Esta pregunta ya tiene una respuesta aquí:
No tengo idea de por qué sucede esto, ya que creo que tengo todo correctamente declarado y definido.
Tengo el siguiente programa, diseñado con plantillas. Es una implementación simple de una cola, con las funciones de miembro "agregar", "restar" e "imprimir".
He definido el nodo para la cola en el fino "nodo_colaypila.h":
#ifndef NODO_COLAYPILA_H
#define NODO_COLAYPILA_H
#include <iostream>
template <class T> class cola;
template <class T> class nodo_colaypila
{
T elem;
nodo_colaypila<T>* sig;
friend class cola<T>;
public:
nodo_colaypila(T, nodo_colaypila<T>*);
};
Luego la implementación en "nodo_colaypila.cpp"
#include "nodo_colaypila.h"
#include <iostream>
template <class T> nodo_colaypila<T>::nodo_colaypila(T a, nodo_colaypila<T>* siguiente = NULL)
{
elem = a;
sig = siguiente;//ctor
}
Después, la definición y declaración de la clase de plantilla de cola y sus funciones:
"cola.h":
#ifndef COLA_H
#define COLA_H
#include "nodo_colaypila.h"
template <class T> class cola
{
nodo_colaypila<T>* ult, pri;
public:
cola<T>();
void anade(T&);
T saca();
void print() const;
virtual ~cola();
};
#endif // COLA_H
"cola.cpp":
#include "cola.h"
#include "nodo_colaypila.h"
#include <iostream>
using namespace std;
template <class T> cola<T>::cola()
{
pri = NULL;
ult = NULL;//ctor
}
template <class T> void cola<T>::anade(T& valor)
{
nodo_colaypila <T> * nuevo;
if (ult)
{
nuevo = new nodo_colaypila<T> (valor);
ult->sig = nuevo;
ult = nuevo;
}
if (!pri)
{
pri = nuevo;
}
}
template <class T> T cola<T>::saca()
{
nodo_colaypila <T> * aux;
T valor;
aux = pri;
if (!aux)
{
return 0;
}
pri = aux->sig;
valor = aux->elem;
delete aux;
if(!pri)
{
ult = NULL;
}
return valor;
}
template <class T> cola<T>::~cola()
{
while(pri)
{
saca();
}//dtor
}
template <class T> void cola<T>::print() const
{
nodo_colaypila <T> * aux;
aux = pri;
while(aux)
{
cout << aux->elem << endl;
aux = aux->sig;
}
}
Entonces, tengo un programa para probar estas funciones de la siguiente manera:
"main.cpp"
#include <iostream>
#include "cola.h"
#include "nodo_colaypila.h"
using namespace std;
int main()
{
float a, b, c;
string d, e, f;
cola<float> flo;
cola<string> str;
a = 3.14;
b = 2.71;
c = 6.02;
flo.anade(a);
flo.anade(b);
flo.anade(c);
flo.print();
cout << endl;
d = "John";
e = "Mark";
f = "Matthew";
str.anade(d);
str.anade(e);
str.anade(f);
cout << endl;
c = flo.saca();
cout << "First In First Out Float: " << c << endl;
cout << endl;
f = str.saca();
cout << "First In First Out String: " << f << endl;
cout << endl;
flo.print();
cout << endl;
str.print();
cout << "Hello world!" << endl;
return 0;
}
Pero cuando construyo, el compilador arroja errores en cada instancia de la clase de plantilla:
referencia indefinida a `cola (float) :: cola () ''... (en realidad es cola'' <''float''> '':: cola (), pero esto no me permite usarlo así).
Y así. En total, 17 advertencias, contando las de las funciones miembro que se llaman en el programa.
¿Por qué es esto? Esas funciones y constructores se definieron. Pensé que el compilador podría reemplazar la "T" en la plantilla con "float", "string" o lo que sea; esa era la ventaja de usar plantillas.
Leí en algún lado que debería poner la declaración de cada función en el archivo de encabezado por algún motivo. ¿Está bien? Y si es así, ¿por qué?
Gracias por adelantado.
Deberá definir las funciones dentro de su archivo de encabezado.
No puede separar la definición de funciones de plantilla en el archivo de origen y las declaraciones en el archivo de encabezado.
Cuando se utiliza una plantilla de una manera que desencadena su intstantation, un compilador necesita ver esa definición de plantillas en particular. Este es el motivo por el que las plantillas a menudo se definen en el archivo de encabezado en el que se declaran.
Referencia:
Estándar C ++ 03, § 14.7.2.4:
La definición de una plantilla de función no exportada, una plantilla de función miembro no exportada, o una función miembro no exportada o un miembro de datos estáticos de una plantilla de clase debe estar presente en cada unidad de traducción en la que está explícitamente instanciada.
EDITAR:
Para aclarar la discusión sobre los comentarios:
Técnicamente, hay tres formas de evitar este problema de vinculación:
- Para mover la definición al archivo .h
- Agregue instancias explícitas en el archivo
.cpp
. -
.cpp
archivo.cpp
que define la plantilla en el archivo.cpp
utilizando la plantilla.
Cada uno de ellos tiene sus pros y sus contras,
Mover las definiciones a los archivos de encabezado puede aumentar el tamaño del código (los compiladores de hoy en día pueden evitarlo) pero seguramente aumentará el tiempo de compilación.
El uso del enfoque de creación de instancias explícitas está volviendo al enfoque tradicional similar a macro. Otra desventaja es que es necesario saber qué tipos de plantilla necesita el programa. Para un programa simple, esto es fácil, pero para el programa complicado esto se vuelve difícil de determinar de antemano.
Si bien incluir archivos cpp es confuso al mismo tiempo, comparte los problemas de ambos enfoques anteriores.
Encuentro que el primer método es el más fácil de seguir y de implementar, y por lo tanto me gusta usarlo.
Esta es una pregunta común en la programación C ++. Hay dos respuestas válidas para esto. Hay ventajas y desventajas en ambas respuestas y su elección dependerá del contexto. La respuesta común es poner toda la implementación en el archivo de encabezado, pero hay otro enfoque que será adecuado en algunos casos. La decisión es tuya.
El código en una plantilla es simplemente un ''patrón'' conocido por el compilador. El compilador no compilará los constructores cola<float>::cola(...)
y cola<string>::cola(...)
hasta que sea forzado a hacerlo. Y debemos asegurarnos de que esta compilación ocurra para los constructores al menos una vez en todo el proceso de compilación, o obtendremos el error de "referencia no definida". (Esto se aplica a los otros métodos de cola<T>
también).
Comprender el problema
El problema es causado por el hecho de que main.cpp
y cola.cpp
se compilarán por separado primero. En main.cpp
, el compilador instanciará implícitamente las clases de plantilla cola<float>
y cola<string>
porque esas instanciaciones particulares se usan en main.cpp
. La mala noticia es que las implementaciones de esas funciones miembro no están en main.cpp
, ni en ningún archivo de encabezado incluido en main.cpp
, y por lo tanto el compilador no puede incluir versiones completas de esas funciones en main.o
Al compilar cola.cpp
, el compilador tampoco compilará esas instancias, porque no hay instancias implícitas o explícitas de cola<float>
o cola<string>
. Recuerde, al compilar cola.cpp
, el compilador no tiene idea de qué instancias se necesitarán; ¡y no podemos esperar que compile para cada tipo para garantizar que este problema nunca suceda! ( cola<int>
, cola<char>
, cola<ostream>
, cola< cola<int> >
... y así sucesivamente ...)
Las dos respuestas son:
- Indique al compilador, al final de
cola.cpp
, qué clases de plantilla concretas serán necesarias, obligándolo a compilarcola<float>
ycola<string>
. - Coloque la implementación de las funciones miembro en un archivo de encabezado que se incluirá cada vez que cualquier otra ''unidad de traducción'' (como
main.cpp
) use la clase de plantilla.
Respuesta 1: crea una instancia explícita de la plantilla y sus definiciones de miembro
Al final de cola.cpp
, debe agregar líneas que ejemplifiquen explícitamente todas las plantillas relevantes, como
template class cola<float>;
template class cola<string>;
y agrega las dos líneas siguientes al final de nodo_colaypila.cpp
:
template class nodo_colaypila<float>;
template class nodo_colaypila<std :: string>;
Esto asegurará que, cuando el compilador compila cola.cpp
, compile explícitamente todo el código de las clases cola<float>
y cola<string>
. Del mismo modo, nodo_colaypila.cpp
contiene las implementaciones de las nodo_colaypila<...>
.
En este enfoque, debe asegurarse de que toda la implementación se coloque en un archivo .cpp
(es decir, una unidad de traducción) y que la .cpp
explícita se coloque después de la definición de todas las funciones (es decir, al final del archivo).
Respuesta 2: copie el código en el archivo de encabezado relevante
La respuesta común es mover todo el código de los archivos de implementación cola.cpp
y nodo_colaypila.cpp
a cola.h
y nodo_colaypila.h
. A la larga, esto es más flexible, ya que significa que puede usar instancias adicionales (por ejemplo, cola<char>
) sin más trabajo. Pero podría significar que las mismas funciones se compilan muchas veces, una vez en cada unidad de traducción. Este no es un gran problema, ya que el enlazador ignorará correctamente las implementaciones duplicadas. Pero podría ralentizar la compilación un poco.
Resumen
La respuesta predeterminada, utilizada por el STL por ejemplo y en la mayoría del código que cualquiera de nosotros escribirá, es poner todas las implementaciones en los archivos de encabezado. Pero en un proyecto más privado, tendrá más conocimiento y control de las clases de plantillas particulares que se crearán instancias. De hecho, este ''error'' podría ser visto como una característica, ya que impide que los usuarios de su código accidentalmente utilicen instancias que no haya probado o planificado ("Sé que esto funciona para cola<float>
y cola<string>
, si desea utilizar algo más, dígame primero y podrá verificar que funcione antes de habilitarlo. ").
Finalmente, hay otros tres errores menores en el código en su pregunta:
- Te falta un
#endif
al final de nodo_colaypila.h - en cola.h
nodo_colaypila<T>* ult, pri;
debería sernodo_colaypila<T> *ult, *pri;
- Ambos son punteros. - nodo_colaypila.cpp: El parámetro predeterminado debe estar en el archivo de cabecera
nodo_colaypila.h
, no en este archivo de implementación.
Este enlace explica dónde te estás equivocando:
Coloque la definición de sus constructores, métodos de destructores y otras cosas en su archivo de encabezado, y eso corregirá el problema.
Esto ofrece otra solución:
¿Cómo puedo evitar errores del enlazador con mis funciones de plantilla?
Sin embargo, esto requiere que anticipe cómo se utilizará su plantilla y, como solución general, no es intuitiva. Sin embargo, resuelve el caso de esquina donde se desarrolla una plantilla para ser utilizada por algún mecanismo interno, y se quiere controlar la manera en que se usa.