Siempre considerando que el siguiente encabezado, que contiene mi clase con plantilla, está incluido en al menos dos .CPP
archivos, este código se compila correctamente:
template <class T>
class TClass
{
public:
void doSomething(std::vector<T> * v);
};
template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
// Do something with a vector of a generic T
}
template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
// Do something with a vector of int's
}
Pero tenga en cuenta el en línea en el método de especialización. Es necesario para evitar un error del enlazador (en VS2008 es LNK2005) debido a que el método se define más de una vez. Entiendo esto porque AFAIK, una especialización de plantilla completa es lo mismo que una definición de método simple.
Entonces, ¿cómo elimino eso inline
? El código no debe duplicarse en cada uso. Busqué en Google, leí algunas preguntas aquí en SO y probé muchas de las soluciones sugeridas, pero ninguna se creó con éxito (al menos no en VS 2008).
¡Gracias!
Respuestas:
Al igual que con las funciones simples, puede usar la declaración y la implementación. Pon tu declaración de encabezado:
template <> void TClass<int>::doSomething(std::vector<int> * v);
y ponga la implementación en uno de sus archivos cpp:
template <> void TClass<int>::doSomething(std::vector<int> * v) { // Do somtehing with a vector of int's }
No olvide eliminar en línea (lo olvidé y pensé que esta solución no funcionará :)). Comprobado en VC ++ 2005
fuente
inline
copiar / pegar. ¡De esta manera funcionó!Necesita mover la definición de especialización al archivo CPP. Se permite la especialización de la función miembro de la clase de plantilla incluso si la función no se declara como plantilla.
fuente
No hay ninguna razón para eliminar la palabra clave en línea.
No cambia el significado del código de ninguna manera.
fuente
inline
palabra clave da como resultado que la función esté realmente insertada (el estándar dice que el compilador debe tomarla como una pista), entonces esas copias adicionales no se pueden eliminar. Sin embargo, es solo una pista para insertar (su efecto principal es decir "no generar errores en las colisiones de enlaces de una manera particular")Si desea eliminar el inline por cualquier motivo, la solución de maxim1000 es perfectamente válida.
En tu comentario, sin embargo, parece que crees que la palabra clave en línea significa que la función con todo su contenido siempre está en línea, pero AFAIK, eso en realidad depende mucho de la optimización de tu compilador.
Citando las preguntas frecuentes de C ++
Entonces, a menos que sepa que esa función realmente inflará su ejecutable o que quiera eliminarlo del encabezado de definición de la plantilla por otras razones, puede dejarlo donde está sin ningún daño.
fuente
Me gustaría agregar que todavía hay una buena razón para mantener la
inline
palabra clave allí si tiene la intención de dejar también la especialización en el archivo de encabezado.Referencia: https://stackoverflow.com/a/4445772/1294184
fuente
Esto es un poco de OT, pero pensé en dejar esto aquí en caso de que ayude a alguien más. Estaba buscando en Google sobre la especialización de plantillas, lo que me llevó aquí, y aunque la respuesta de @ maxim1000 es correcta y, en última instancia, me ayudó a resolver mis problemas, no pensé que estuviera muy claro.
Mi situación es un poco diferente (pero lo suficientemente similar como para dejar esta respuesta, creo) que la de los OP. Básicamente, estoy usando una biblioteca de terceros con diferentes tipos de clases que definen "tipos de estado". El corazón de estos tipos son simplemente
enum
s, pero todas las clases heredan de un padre común (abstracto) y proporcionan diferentes funciones de utilidad, como la sobrecarga del operador y unastatic toString(enum type)
función. Cada estadoenum
es diferente entre sí y no está relacionado. Por ejemplo, unoenum
tiene los camposNORMAL, DEGRADED, INOPERABLE
, otro tieneAVAILBLE, PENDING, MISSING
, etc. Mi software se encarga de gestionar diferentes tipos de estados para diferentes componentes. Sucedió que quería utilizar lastoString
funciones para estosenum
clases, pero como son abstractas no pude instanciarlas directamente. Podría haber extendido cada clase que quería usar, pero finalmente decidí crear unatemplate
clase, dondetypename
sería cualquier estado concreto queenum
me importara. Probablemente se pueda tener algún debate sobre esa decisión, pero sentí que eso era mucho menos trabajo que extender cadaenum
clase abstracta con una personalizada e implementar las funciones abstractas. Y, por supuesto, en mi código, solo quería poder llamar.toString(enum type)
y hacer que imprima la representación de cadena de esoenum
. Dado que todos losenum
s no tenían ninguna relación, cada uno tenía su propiotoString
funciones que (después de algunas investigaciones que aprendí) tenían que llamarse usando la especialización de plantillas. Eso me trajo aquí. A continuación se muestra un MCVE de lo que tuve que hacer para que esto funcione correctamente. Y en realidad mi solución fue un poco diferente a la de @ maxim1000.Este es un archivo de encabezado (muy simplificado) para el
enum
s. En realidad, cadaenum
clase se definió en su propio archivo. Este archivo representa los archivos de encabezado que se me proporcionan como parte de la biblioteca que estoy usando:// file enums.h #include <string> class Enum1 { public: enum EnumerationItem { BEARS1, BEARS2, BEARS3 }; static std::string toString(EnumerationItem e) { // code for converting e to its string representation, // omitted for brevity } }; class Enum2 { public: enum EnumerationItem { TIGERS1, TIGERS2, TIGERS3 }; static std::string toString(EnumerationItem e) { // code for converting e to its string representation, // omitted for brevity } };
agregando esta línea solo para separar el siguiente archivo en un bloque de código diferente:
// file TemplateExample.h #include <string> template <typename T> class TemplateExample { public: TemplateExample(T t); virtual ~TemplateExample(); // this is the function I was most concerned about. Unlike @maxim1000's // answer where (s)he declared it outside the class with full template // parameters, I was able to keep mine declared in the class just like // this std::string toString(); private: T type_; }; template <typename T> TemplateExample<T>::TemplateExample(T t) : type_(t) { } template <typename T> TemplateExample<T>::~TemplateExample() { }
siguiente archivo
// file TemplateExample.cpp #include <string> #include "enums.h" #include "TemplateExample.h" // for each enum type, I specify a different toString method, and the // correct one gets called when I call it on that type. template <> std::string TemplateExample<Enum1::EnumerationItem>::toString() { return Enum1::toString(type_); } template <> std::string TemplateExample<Enum2::EnumerationItem>::toString() { return Enum2::toString(type_); }
siguiente archivo
// and finally, main.cpp #include <iostream> #include "TemplateExample.h" #include "enums.h" int main() { TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1); TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3); std::cout << t1.toString() << std::endl; std::cout << t2.toString() << std::endl; return 0; }
y esto produce:
No tengo idea de si esta es la solución ideal para resolver mi problema, pero funcionó para mí. Ahora, no importa cuántos tipos de enumeración termine usando, todo lo que tengo que hacer es agregar algunas líneas para el
toString
método en el archivo .cpp, y puedo usar eltoString
método de bibliotecas ya definido sin implementarlo yo mismo y sin extender cadaenum
clase que quiero usar.fuente