Tengo un código de plantilla que preferiría haber almacenado en un archivo CPP en lugar de en línea en el encabezado. Sé que esto se puede hacer siempre que sepa qué tipos de plantillas se utilizarán. Por ejemplo:
archivo .h
class foo
{
public:
template <typename T>
void do(const T& t);
};
archivo .cpp
template <typename T>
void foo::do(const T& t)
{
// Do something with t
}
template void foo::do<int>(const int&);
template void foo::do<std::string>(const std::string&);
Tenga en cuenta las dos últimas líneas: la función de plantilla foo :: do solo se usa con ints y std :: strings, por lo que esas definiciones significan que la aplicación se vinculará.
Mi pregunta es: ¿es un truco desagradable o funcionará con otros compiladores / enlazadores? Solo estoy usando este código con VS2008 en este momento, pero querré portar a otros entornos.
do
como identificador: ptemplate class foo<int>;template class foo<std::string>;
al final del archivo .cpp?Respuestas:
El problema que describe puede resolverse definiendo la plantilla en el encabezado o mediante el enfoque que describió anteriormente.
Recomiendo leer los siguientes puntos de C ++ FAQ Lite :
Entran en muchos detalles sobre estos (y otros) problemas de plantilla.
fuente
Para otros en esta página que se preguntan cuál es la sintaxis correcta (como lo hice yo) para la especialización explícita de plantillas (o al menos en VS2008), es la siguiente ...
En su archivo .h ...
Y en tu archivo .cpp
fuente
Este código está bien formado. Solo tiene que prestar atención a que la definición de la plantilla sea visible en el punto de creación de instancias. Para citar el estándar, § 14.7.2.4:
fuente
a.cpp
(definiendo la funcióna() {}
) yb.cpp
(definiendo la funciónb() { a() }
), esto se vinculará con éxito. Si estoy en lo cierto, entonces la cita anterior parecería no aplicarse para el caso típico ... ¿Me estoy equivocando en alguna parte?inline
funcionesinline
. La razón es que sin un ABI C ++ estandarizado es difícil / imposible definir el efecto que esto tendría de otra manera.Esto debería funcionar bien en todas las plantillas compatibles. La creación de instancias de plantilla explícita es parte del estándar C ++.
fuente
Su ejemplo es correcto pero no muy portátil. También hay una sintaxis un poco más limpia que se puede usar (como lo señala @ namespace-sid).
Supongamos que la clase con plantilla es parte de alguna biblioteca que se va a compartir. ¿Deberían compilarse otras versiones de la clase con plantilla? ¿Se supone que el mantenedor de la biblioteca anticipa todos los posibles usos de plantilla de la clase?
Un enfoque alternativo es una ligera variación de lo que tiene: agregue un tercer archivo que es el archivo de implementación / instanciación de plantilla.
archivo foo.h
archivo foo.cpp
archivo foo-impl.cpp
La única advertencia es que necesita decirle al compilador que compile en
foo-impl.cpp
lugar de hacerlo,foo.cpp
ya que la compilación no hace nada.Por supuesto, puede tener múltiples implementaciones en el tercer archivo o tener múltiples archivos de implementación para cada tipo que le gustaría usar.
Esto permite mucha más flexibilidad al compartir la clase de plantilla para otros usos.
Esta configuración también reduce los tiempos de compilación para las clases reutilizadas porque no está volviendo a compilar el mismo archivo de encabezado en cada unidad de traducción.
fuente
foo.cpp
) de las versiones que realmente se compilan (enfoo-impl.cpp
) y declaraciones (enfoo.h
). No me gusta que la mayoría de las plantillas de C ++ se definan completamente en archivos de encabezado. Eso es contrario al estándar C / C ++ de pares dec[pp]/h
para cada clase / espacio de nombres / cualquier agrupación que use. Parece que las personas todavía usan archivos de encabezado monolíticos simplemente porque esta alternativa no se usa ni se conoce ampliamente.h/cpp
par, aunque tuve que rodear la lista original de instancias en un protector de inclusión, pero aún podía compilar elfoo.cpp
normal. Sin embargo, todavía soy bastante nuevo en C ++ y me interesaría saber si este uso mixto tiene alguna advertencia adicional.foo.cpp
yfoo-impl.cpp
. No#include "foo.cpp"
en elfoo-impl.cpp
archivo; en su lugar, agregue la declaraciónextern template class foo<int>;
parafoo.cpp
evitar que el compilador cree instancias de la plantilla al compilarfoo.cpp
. Asegúrese de que el sistema de compilación compila ambos.cpp
archivos y pasa ambos archivos de objeto al vinculador. Esto tiene múltiples beneficios: a) está claro enfoo.cpp
que no hay instanciación; b) los cambios en foo.cpp no requieren una compilación de foo-impl.cpp.foo.cpp
enfoo_impl.h
yfoo-impl.cpp
en solofoo.cpp
. También agregaría typedefs para instancias defoo.cpp
afoo.h
, del mismo modousing foo_int = foo<int>;
. El truco es proporcionar a los usuarios dos interfaces de encabezado para elegir. Cuando el usuario necesita una instanciación predefinida que incluyefoo.h
, cuando el usuario necesita algo fuera de orden que incluyefoo_impl.h
.Esto definitivamente no es un truco desagradable, pero tenga en cuenta el hecho de que tendrá que hacerlo (la especialización explícita de la plantilla) para cada clase / tipo que desee utilizar con la plantilla dada. En el caso de MUCHOS tipos que solicitan la creación de instancias de plantilla, puede haber MUCHAS líneas en su archivo .cpp. Para solucionar este problema, puede tener un TemplateClassInst.cpp en cada proyecto que use para que tenga un mayor control sobre los tipos que se instanciarán. Obviamente, esta solución no será perfecta (también conocida como bala de plata), ya que podría terminar rompiendo el ODR :).
fuente
Hay, en el último estándar, una palabra clave (
export
) que ayudaría a aliviar este problema, pero no está implementado en ningún compilador que conozca, aparte de Comeau.Consulte las preguntas frecuentes sobre esto.
fuente
Esa es una forma estándar de definir funciones de plantilla. Creo que hay tres métodos que leo para definir plantillas. O probablemente 4. Cada uno con pros y contras.
Definir en la definición de clase. No me gusta para nada porque creo que las definiciones de clase son estrictamente de referencia y deberían ser fáciles de leer. Sin embargo, es mucho menos complicado definir plantillas en clase que fuera. Y no todas las declaraciones de plantilla están en el mismo nivel de complejidad. Este método también hace que la plantilla sea una plantilla verdadera.
Defina la plantilla en el mismo encabezado, pero fuera de la clase. Esta es mi forma preferida la mayoría de las veces. Mantiene su definición de clase ordenada, la plantilla sigue siendo una plantilla verdadera. Sin embargo, requiere un nombre completo de la plantilla que puede ser complicado. Además, su código está disponible para todos. Pero si necesita que su código esté en línea, esta es la única manera. También puede lograr esto creando un archivo .INL al final de sus definiciones de clase.
Incluya el header.h y la implementación.CPP en su main.CPP. Creo que así es como se hace. No tendrá que preparar ninguna instancia previa, se comportará como una plantilla verdadera. El problema que tengo es que no es natural. Normalmente no incluimos y esperamos incluir archivos de origen. Supongo que dado que incluiste el archivo fuente, las funciones de la plantilla se pueden incorporar.
Este último método, que era la forma publicada, es definir las plantillas en un archivo fuente, al igual que el número 3; pero en lugar de incluir el archivo fuente, crearemos una instancia previa de las plantillas que necesitaremos. No tengo ningún problema con este método y a veces resulta útil. Tenemos un código grande, no puede beneficiarse de estar en línea, así que solo póngalo en un archivo CPP. Y si conocemos instancias comunes y podemos predefinirlas. Esto nos salva de escribir básicamente lo mismo 5, 10 veces. Este método tiene la ventaja de mantener nuestro código patentado. Pero no recomiendo poner pequeñas funciones de uso regular en los archivos CPP. Como esto reducirá el rendimiento de su biblioteca.
Tenga en cuenta que no conozco las consecuencias de un archivo obj hinchado.
fuente
Sí, esa es la forma estándar de
especialización.instanciación explícita. Como indicó, no puede crear una instancia de esta plantilla con otros tipos.Editar: corregido en función del comentario.
fuente
Tomemos un ejemplo, digamos por alguna razón que desea tener una clase de plantilla:
Si compila este código con Visual Studio, funciona de forma inmediata. gcc producirá un error de enlazador (si se usa el mismo archivo de encabezado de múltiples archivos .cpp):
Es posible mover la implementación al archivo .cpp, pero luego debe declarar una clase como esta:
Y luego .cpp se verá así:
Sin dos últimas líneas en el archivo de encabezado, gcc funcionará bien, pero Visual Studio producirá un error:
la sintaxis de clase de plantilla es opcional en caso de que desee exponer la función a través de la exportación .dll, pero esto es aplicable solo para la plataforma de Windows, por lo que test_template.h podría verse así:
con el archivo .cpp del ejemplo anterior.
Sin embargo, esto le da más dolor de cabeza al enlazador, por lo que se recomienda usar el ejemplo anterior si no exporta la función .dll.
fuente
¡Hora de una actualización! Cree un archivo en línea (.inl, o probablemente cualquier otro) y simplemente copie todas sus definiciones en él. Asegúrese de agregar la plantilla sobre cada función (
template <typename T, ...>
). Ahora, en lugar de incluir el archivo de encabezado en el archivo en línea, haga lo contrario. Incluya el archivo en línea después de la declaración de su clase (#include "file.inl"
).Realmente no sé por qué nadie ha mencionado esto. No veo inconvenientes inmediatos.
fuente
#include "file.inl"
, el preprocesador va a pegar el contenidofile.inl
directamente en el encabezado. Cualquiera sea la razón por la que deseaba evitar la implementación en el encabezado, esta solución no resuelve ese problema.template
definiciones fuera de línea . Entiendo por qué la gente quiere hacerlo, para lograr la mayor paridad con declaraciones / definiciones que no sean plantillas, para mantener la declaración de la interfaz ordenada, etc., pero no siempre vale la pena. Se trata de evaluar las compensaciones en ambos lados y elegir lo menos malo . ... hasta que senamespace class
convierta en una cosa: O [ por favor, sé una cosa ]No hay nada malo con el ejemplo que ha dado. Pero debo decir que creo que no es eficiente almacenar definiciones de funciones en un archivo cpp. Solo entiendo la necesidad de separar la declaración y la definición de la función.
Cuando se usa junto con la creación de instancias de clase explícita, la Biblioteca de verificación de conceptos de Boost (BCCL) puede ayudarlo a generar código de función de plantilla en archivos cpp.
fuente
Ninguno de los anteriores funcionó para mí, así que así es como lo resolví, mi clase solo tiene 1 método.
.h
.cpp
esto evita errores del enlazador y no es necesario llamar a TemporaryFunction en absoluto
fuente