2009-10-28 14 views
31

Tengo muy poca idea de lo que ocurre con las plantillas C++, pero estoy intentando implementar una función que busque un elemento que satisfaga una propiedad determinada (en este caso, buscar) para uno con el nombre dado). Mi declaración en mi archivo .h es el siguiente:El problema de la plantilla provoca un error del enlazador (C++)

template <typename T> 
T* find_name(std::vector<T*> v, std::string name); 

Cuando compilar, obtengo este error de vinculador cuando llamo a la función:

Error 1 error LNK2019: unresolved external symbol "class Item * __cdecl find_name<class Item>(class std::vector<class Item *,class std::allocator<class Item *> >,class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" ([email protected]@@@@[email protected]@[email protected]@@[email protected]@@@[email protected]@@[email protected]@[email protected][email protected]@[email protected]@[email protected]@[email protected]@[email protected]@Z) referenced in function "public: class Item * __thiscall Place::get_item(class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >)" ([email protected]@@[email protected]@[email protected][email protected]@[email protected]@[email protected]@[email protected]@[email protected]@@Z) place.obj Program2 

Una vez más, soy nuevo en las plantillas por lo que no sé lo que está pasando. Todas las instancias que he encontrado de LNK2019 a través de Google han consistido en no utilizar las bibliotecas correctas, pero dado que esta es mi propia función, no veo por qué sucedería esto.

Además, una pregunta relacionada: ¿Hay alguna manera de hacer un parámetro de plantilla para que tenga que ser una subclase de una cierta clase, es decir, una plantilla?

+0

¿qué compilador estás usando? algunos compiladores evitan que separe la declaración y la definición en archivos separados para las plantillas. – Jordan

+0

¿Escribiste realmente una implementación para tu función de plantilla? – begray

+2

También puede considerar usar std :: find o std :: find_if –

Respuesta

59

Tiene que tener sus definiciones de plantilla disponibles en el sitio de la llamada. Eso significa que no hay archivos .cpp.

El motivo es que las plantillas no se pueden compilar. Piensa en las funciones como cookies, y el compilador es un horno.

Las plantillas son solo un cortador de galletas, porque no saben qué tipo de cookie son. Solo le dice al compilador cómo hacer la función cuando se le da un tipo, pero en sí misma no se puede usar porque no se está operando ningún tipo concreto. No puedes cocinar un cortador de galletas. Solo cuando tenga preparada la sabrosa masa para galletas (es decir, dado el compilador, la masa [tipo])) puede cortar la galleta y cocinarla.

Del mismo modo, solo cuando realmente utiliza la plantilla con un tipo determinado, el compilador puede generar la función real y compilarla. Sin embargo, no puede hacer esto si falta la definición de la plantilla. Tienes que moverlo al archivo de cabecera, para que la persona que llama de la función pueda crear la cookie.

+0

Gracias. Ahora lo entiendo, pero me temo que no entiendo muy bien por qué puede hacer esto para las funciones normales, pero no para las funciones de plantilla, que supongo que puedo atribuir a una comprensión incompleta de C++. – marsolk

+0

Las plantillas son plantillas, algo así como macros, simplemente mejor (y peor;). Hasta que, a menos que los use, el compilador no necesita hacer un reemplazo tipo macro con el tipo (s) dado (s) y crear la función real. Ahora, cuando el compilador golpea esta creación de instancias, puede o no tener la regla repetitiva en la mano. Si no, golpeas una barricada. – dirkgently

+1

Las plantillas de funciones son * no * funciones. Las plantillas de función son * plantillas *. Ellos obedecen sus propias reglas. – AnT

0

¿Pusiste la definición de función de tu plantilla en un archivo cpp? Luego muévelo al encabezado y enlínealo.

+0

No, esto no es obligatorio. Vea la respuesta de Charles Bailey arriba. –

29

Probablemente esté perdiendo una instanciación válida. Si coloca su definición de plantilla en un archivo .cpp separado, cuando el compilador compila ese archivo, es posible que no sepa qué instancias necesita. Por el contrario, en los sitios de llamadas que instanciarían la versión correcta de la función de plantilla, si la definición del cuerpo de la función no está disponible, el compilador no tendrá la información para instanciar las especializaciones requeridas.

Tiene dos opciones. Coloque el cuerpo de la función para la plantilla de función en el archivo de encabezado.

p. Ej. en el archivo de encabezado:

template <typename T> 
inline T* find_name(std::vector<T*> v, std::string name) 
{ 
    // ... 
} 

o crea una instancia explícita de la plantilla en el archivo .cpp donde definió la plantilla.

p. Ej. en el archivo de origen (probablemente requerirá #include ing del archivo que define Item):

template <typename T> 
T* find_name(std::vector<T*> v, std::string name) 
{ 
    // ... 
} 

template Item* find_name<Item>(std::vector<Item*> v, std::string name); 
+1

Encuentro esto más útil que la respuesta aceptada debido a ese gran consejo sobre forzar la creación de instancias en el archivo C++. – ancientHacker

9

Las respuestas aquí son grandes.

Voy a agregar que esto es a menudo por qué además de los archivos .h y .cpp en un proyecto. A menudo encontrará .inl archivos. Las definiciones de plantilla irán al archivo .inl.

Estos archivos .inl significan en línea y generalmente se incluirán en el archivo .h con el mismo prefijo en la parte inferior del archivo después de todas las declaraciones del encabezado. Esto los convierte efectivamente en parte del archivo de encabezado, pero separa las declaraciones de las definiciones.

Ya que son glorificados archivos de cabecera se deben tomar todas las precauciones que lo haría con un archivo de cabecera regular, es decir, incluir guardias etc.

+0

¿Estos archivos '.inl' son compatibles con el estándar C++ o son dependientes del compilador? – kim366

0

Acabo de notar que tenía una segunda pregunta, que parece ser sin respuesta:

¿hay una manera de hacer un parámetro de plantilla de modo que tiene que ser una subclase de una clase determinada, es decir plantilla?

Es posible. Por ejemplo, vea is_base_of en Boost.TypeTraits.

Sin embargo, tengo curiosidad: ¿por qué quieres eso? Normalmente, los requisitos de una plantilla en sus parámetros no están en el tipo del parámetro en sí, sino en las expresiones que implican ese tipo son legales. Por ejemplo, imagina que tienes:

template<class T> 
void foo(const T& t) 
{ 
    if (t.foo()){ 
     t.bar("blah"); 
    } 
} 

Decir que T debe heredar de algo como:

class HasFooAndBar 
{ 
public: 
    void foo()const; 
    void bar(const char*)const; 
}; 

no aporta nada, porque la creación de instancias de la función fallará todos modos, si el tipo no es compatible con las operaciones . Además, restringe innecesariamente la aplicabilidad de foo(). De hecho, de los Foo los requisitos son que t.foo()and t.bar(const char*) son expresiones válidas en un T. const Por ejemplo, este tipo no hereda de HasFooAndBar y sigue siendo un foo válida() parámetros:

struct DifferentFromHasFooAndBar 
{ 
    bool foo()const; 
    std::string bar(const std::string&)const; 
}; 
2

Tropezamos con el mismo tema y encontró esto que indica 3 soluciones: http://www.codeproject.com/Articles/48575/How-to-define-a-template-class-in-a-h-file-and-imp

Entre ellas es una fácil donde se crea un método "ficticio" en el archivo .cpp, que llama a la función plantilla/clase con los diferentes tipos. Pegado desde el enlace:

// No need to call this TemporaryFunction() function, it's just to avoid link error. 
void TemporaryFunction() 
{ 
    TestTemp<int> TempObj; 
    TestTemp<float> TempObj2; 
} 
+2

Me gusta mucho este método, pero ¿hay alguna forma de asegurarse de que el compilador no optimice esto? Una vez probé -O3 optimización, y luego 'símbolo indefinido' – darwinsenior

Cuestiones relacionadas