Las funciones, métodos, campos y variables que dependen de parámetros de plantillas, tanto en su declaración como en su código, no se deben definir en el archivo .cpp, deben quedar definidos en el header (.h).
Este asunto no es baladí y es un coladero de bugs importante para la programación en este lenguaje por muchos motivos, pero te comento varios problemas típicos.
Nota: En adelante se distinguen las declaraciones (.h), que son como una guía para el compilador; y las definiciones (.cpp), que será el código fuente final compilado que se enlazará por/entre otros objetos para formar una aplicación (exe) o una librería (dll, lib, ...).
Debes comprender que C++ es un lenguaje de enlace estático en tiempo de compilación, es decir, todas las declaraciones de tipos se interpretan, se recrean y enlazan entre sí en tiempo de la compilación de la "imagen" ejecutable resultante, y no se puede modificar ni un solo bit para que cumpla su función correctamente. Las plantillas, y las definiciones de sus miembros (campos, métodos, subclases, etc.), son tipos genéricos que dependen de parámetros para que sean declarados y su definición sólo se compila cuando se hace referencia a ellas en el código. Como los archivos .cpp se compilan por separado como objetos independientes (aunque después se vinculen para localizar las referencias simbólicas declaradas en el header), la compilación de las definiciones genéricas de un archivo .cpp no definen a priori las declaraciones de tipos de otros archivos .cpp.
Por ejemplo en tu caso, el archivo de código fuente (.cpp) que contiene la plantilla Escuela
dependiente del parámetro V1
se compila antes e independientemente al objeto .cpp que contiene la función main
sin haber encontrado ninguna declaración que permita recrear el código fuente, por tanto se omiten las definiciones y el linker no encontrará los símbolos que requiere el objeto .cpp de main
.
¿Te imaginas que se tuvieran que compilar todos los diferentes comportamientos del código fuente de las definiciones de la plantilla Escuela<V1>
y del resto de plantillas de todos los tipos que se incluyen en todas las librerías de C++, y relacionarlas entre sí mismas, por si en algún caso las necesitas? Es como un cerdo volador pero sin el como. Esto sólo es posible en lenguajes de enlace dinámico a través de metadatos de tipos como Java, C#, VB (.NET) y análogos.
Si incluyes en el archivo .cpp de Escuela
una variable de tipo Escuela<std:string>
(fuera de los métodos) o una directiva using TipoEcuela = Escuela<std:string>;
, el tipo quedaría declarado, definido, compilado y accesible desde otros objetos .cpp, pero no es recomendable en absoluto. Cualquier cambio en las versiones de las librerías enlazadas e incluso en el sistema, provocaría un comportamiento incontrolado con un final catastrófico y, probablemente, con otro programador engordando las listas del paro.
Hay varias soluciones
Lo más sencillo es definir en la misma declaración de la plantilla, conocido como inline definition:
/*clase.h*/
#ifndef CLASE_H
#define CLASE_H
template <class V1>
class Escuela
{
V1 nombre;
public:
Escuela(V1 a): nombre(a){};
V1 getN() const
{
return nombre;
};
void SetN(V1 a)
{
this -> nombre = a;
};
};
#endif //CLASE_H
También puedes incluir las definiciones tal y como lo harías en el .cpp a continuación de la declaración.
O bien puedes poner las definiciones en otro archivo que no sea .c, .cc, .cxx o .cpp e incluirlo en el .h que contiene la declaración.
Una técnica elegante para mantener la coherencia del estilo entre tipos genéricos y no genéricos (y mi preferencia) es crear un archivo .tpp con las definiciones e incluirlo tras la declaración de la plantilla en el .h
emulando el estilo de los archivos de código fuente .cpp
. Desde hace tiempo la mayoría de entornos de programación reconocen los archivos .tpp como archivos de declaraciones de C++ o headers.
Por el contrario, los métodos de clases que no sean dependientes de parámetros genéricos (plantillas) y no sean inline
o static
siempre se deben definir en los archivos .cpp para que la compilación no duplique sus definiciones en cada objeto .cpp que incluya una referencia. Las clases con herencia virtual
y callbacks desde DLLs pueden generar más catástrofes y más desempleo.
Los métodos inline
, también deben ser declarados y definidos en el .h para que su comportamiento sea el esperado, de lo contrario se considera una función como otra cualquiera y más problemas.
¿Por qué C y C++ es así y no es más flexible?
Porque todo esto permite que una aplicación cualquiera vuele como si estuviese programada en lenguaje máquina o ensamblador, pero con un lenguaje comprensible para las personas y con una mínima preocupación por la optimización de los procesos, los procesadores, de la memoria y de las cientos de características entre diferentes plataformas, procesadores, placas, chips, PCs, móviles, etc.