2008-09-22 21 views
343

Tengo un código de plantilla que preferiría tener 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 plantilla se utilizarán. Por ejemplo:Almacenamiento de definiciones de función de plantilla C++ en un archivo .CPP

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&); 

Nota las dos últimas líneas - la función de plantilla foo :: hacerlo sólo se utiliza con enteros y std :: strings, entonces esas definiciones significan que la aplicación enlazará.

Mi pregunta es: ¿es esto un hack desagradable o esto funcionará con otros compiladores/vinculadores? Solo estoy usando este código con VS2008 en este momento pero querré portar a otros entornos.

+15

No tenía idea de que esto fuera posible, ¡un truco interesante! Hubiera sido útil para algunas tareas recientes saber esto, ¡salud! – xan

+34

Lo que me sobresalta es el uso de 'do' como identificador: p – Quentin

+0

he hecho algo similar con gcc, pero sigo investigando – Nick

Respuesta

147

El problema que describe puede resolverse definiendo la plantilla en el encabezado o mediante el enfoque que describa arriba.

recomiendo la lectura de los siguientes puntos de la C++ FAQ Lite:

Entran en una gran cantidad de detalles acerca de estos (y otros) problemas de plantilla .

+26

Simplemente para complementar la respuesta, el enlace al que se hace referencia responde positivamente a la pregunta, es decir, es posible hacer lo que Rob sugirió y tener el código para ser portátil. – ivotron

+79

¿Puedes simplemente publicar las partes relevantes en la respuesta en sí? ¿Por qué tales referencias están permitidas en SO? No tengo ni idea de qué buscar en este enlace, ya que ha cambiado mucho desde entonces. – Ident

3

Sí, esa es la forma estándar de hacer especialización instanciación explícita. Como dijiste, no puedes crear una instancia de esta plantilla con otros tipos.

Editar: corregido en base al comentario.

+0

Ser exigente con la terminología es una "instanciación explícita". –

10

Esto debería funcionar bien en todas las plantillas compatibles. La instanciación de plantilla explícita es parte del estándar de C++.

4

Hay, en el último estándar, una palabra clave (export) que podría ayudar a aliviar este problema, pero no está implementado en ningún compilador del que sea consciente, aparte de Comeau.

Consulte el FAQ-lite acerca de esto.

+2

AFAIK, la exportación está muerta porque se enfrentan a problemas más nuevos y más nuevos, cada vez que resuelven el último, lo que hace que la solución general sea cada vez más complicada. Y la palabra clave "exportar" no le permitirá "exportar" desde un CPP de todos modos (aún de H. Sutter de todos modos). Entonces digo: no contengan la respiración ... – paercebal

+2

Para implementar la exportación, el compilador aún requiere la definición de plantilla completa. Todo lo que gana es tenerlo en una forma compilada. Pero realmente no tiene sentido. –

+2

... y * desapareció * del estándar, debido a una complicación excesiva para una ganancia mínima. – DevSolar

12

Este código está bien formado. Solo debe prestar atención a que la definición de la plantilla sea visible en el momento de la creación de instancias. Para citar la norma, § 14.7.2.4:

La definición de una plantilla no exportada función, una plantilla no exportada función miembro, o una función no exportada miembro o miembro de datos estáticos de una plantilla de clase deberá estar presente en cada unidad de traducción en la que está explícitamente instanciado.

+1

¿Qué significa * no exportado *? –

+1

@Dan Visible solo dentro de su unidad de compilación, no fuera de ella. Si vincula varias unidades de compilación juntas, los símbolos exportados se pueden usar en ellas (y deben tener una sola, o al menos, en el caso de las plantillas, definiciones consistentes; de lo contrario, se encontrará con UB). –

+0

Gracias. Pensé que todas las funciones están (por defecto) visibles fuera de la unidad de compilación. Si tengo dos unidades de compilación 'a.cpp' (definiendo la función' a() {} ') y' b.cpp' (definiendo la función 'b() {a()}'), esto enlazará con éxito . Si estoy en lo cierto, entonces la cita anterior parece no aplicarse para el caso típico ... ¿Me estoy equivocando en alguna parte? –

0

No hay nada de malo en 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 explícitas de clases, la Biblioteca de comprobación de conceptos de Boost (BCCL) puede ayudarlo a generar código de función de plantilla en archivos cpp.

+6

¿Qué es ineficiente al respecto? –

5

Definitivamente no es un hack desagradable, pero tenga en cuenta el hecho de que tendrá que hacerlo (la especialización de plantilla explícita) para cada clase/tipo que desee utilizar con la plantilla dada. En el caso de que MUCHOS tipos soliciten la creación de instancias de plantillas, puede haber MUCHAS líneas en su archivo .cpp. Para solucionar este problema, puede tener una TemplateClassInst.cpp en cada proyecto que use, de modo que tenga un mayor control sobre qué tipos se crearán instancias. Obviamente, esta solución no será perfecta (también conocida como bala de plata) ya que podrías terminar rompiendo el ODR :).

+0

¿Estás seguro de que romperá la ODR? Si las líneas de creación de instancias en TemplateClassInst.cpp se refieren al archivo fuente idéntico (que contiene las definiciones de función de plantilla), ¿no se garantiza que no violará el ODR ya que todas las definiciones son idénticas (incluso si se repiten)? –

81

Para otros en esta página preguntando qué la sintaxis correcta es (al igual que yo) de especialización de plantilla explícita (o al menos en VS2008), su siguiente ...

En su archivo .h ...

template<typename T> 
class foo 
{ 
public: 
    void bar(const T &t); 
}; 

Y en su archivo .cpp

template <class T> 
void foo<T>::bar(const T &t) 
{ } 

// Explicit template instantiation 
template class foo<int>; 
+5

¿Quiere decir "especialización explícita de plantilla CLASS"? En ese caso, ¿eso cubrirá todas las funciones que tiene la clase con plantilla? – Arthur

-2

tiempo de actualizar! 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, haces 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.

+23

Los inconvenientes inmediatos es que es básicamente lo mismo que simplemente definir las funciones de la plantilla directamente en el encabezado. Una vez que '#include" file.inl "', el preprocesador va a pegar el contenido de 'file.inl' directamente en el encabezado. Cualquiera sea la razón por la que quería evitar que la implementación vaya en el encabezado, esta solución no resuelve ese problema. –

+2

- _and_ significa que usted, técnicamente, se carga innecesariamente con la tarea de escribir todo el texto repetitivo y prolijo que necesitan las definiciones de 'plantilla '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 en buen estado, etc.- pero no siempre vale la pena la molestia. Es un caso de evaluar las compensaciones en ambos lados y escoger el _al menos mal_. ... hasta que ['namespace class'] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0223r0.html) se convierta en una cosa: O [_por favor se una cosa] –

+1

@underscore_d que definitivamente debería convertirse en algo ... – Andrew

5

Su ejemplo es correcto pero no muy portátil. También hay una sintaxis ligeramente más clara 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. ¿Deben compilarse otras versiones de la clase con plantilla? ¿Se supone que el mantenedor de la biblioteca debe anticipar todos los usos posibles 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 la plantilla.

archivo foo.h

// Standard header file guards omitted 

template <typename T> 
class foo 
{ 
public: 
    void bar(const T& t); 
}; 

archivo foo.cpp

// Always include your headers 
#include "foo.h" 

template <typename T> 
void foo::bar(const T& t) 
{ 
    // Do something with t 
} 

archivo foo-impl.cpp

// Yes, we include the .cpp file 
#include "foo.cpp" 
template class foo<int>; 

La única advertencia es que necesita decirle al compilador que compile foo-impl.cpp en lugar de 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 desee utilizar.

Esto permite mucha más flexibilidad cuando se comparte 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á recompilando el mismo archivo de encabezado en cada unidad de traducción.

+0

¿Qué te compra esto? Aún necesita editar foo-impl.cpp para agregar una nueva especialización. –

+0

Separación de detalles de implementación (también conocidas como definiciones en 'foo.cpp') a partir de las cuales se compilan las versiones (en' foo-impl.cpp') y declaraciones (en 'foo.h'). No me gusta que la mayoría de las plantillas de C++ se definan por completo en archivos de encabezado. Esto es contrario al estándar C/C++ de pares de 'c [pp]/h' para cada clase/espacio de nombres/agrupación que use. La gente parece seguir usando archivos de encabezado monolíticos simplemente porque esta alternativa no se usa ni se conoce ampliamente. –

Cuestiones relacionadas