Almacenamiento de definiciones de funciones de plantilla de C++ en un archivo .CPP

StackOverflow https://stackoverflow.com/questions/115703

  •  02-07-2019
  •  | 
  •  

Pregunta

Tengo un código de plantilla que preferiría almacenar 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 este un truco desagradable o funcionará con otros compiladores/enlazadores?Por el momento solo estoy usando este código con VS2008, pero querré trasladarlo a otros entornos.

¿Fue útil?

Solución

El problema que describe se puede resolver definiendo la plantilla en el encabezado o mediante el enfoque que describe anteriormente.

Recomiendo leer los siguientes puntos del Preguntas frecuentes sobre C++ Lite:

Entran en muchos detalles sobre estos (y otros) problemas de plantilla.

Otros consejos

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 tu archivo .h...

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

Y en tu archivo .cpp

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

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

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

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

Esto debería funcionar bien en todos los lugares donde se admitan plantillas.La creación de instancias de plantillas explícitas es parte del estándar C++.

Su ejemplo es correcto pero no muy portátil.También se puede utilizar una sintaxis un poco más limpia (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 debe anticipar todos los posibles usos de plantilla de la clase?

Un enfoque alternativo es una ligera variación de lo que tienes:agregue un tercer archivo que es el archivo de implementación/creación de instancias 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 necesitas decirle al compilador que compile foo-impl.cpp en lugar de foo.cpp ya que compilar este último 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 al compartir la clase con plantilla para otros usos.

Esta configuración también reduce los tiempos de compilación para las clases reutilizadas porque no se vuelve a compilar el mismo archivo de encabezado en cada unidad de traducción.

Definitivamente, este no es un truco 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 usar 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 utilice para tener un mayor control de los tipos de los que se crearán instancias.Obviamente, esta solución no será perfecta (también conocida como solución milagrosa), ya que podría terminar rompiendo el ODR :).

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 yo sepa, aparte de Comeau.

Ver el Preguntas frecuentes ligeras sobre esto.

Sí, esa es la forma estándar de hacerlo. 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.

Esa es una forma estándar de definir funciones de plantilla.Creo que leí tres métodos para definir plantillas.O probablemente 4.Cada uno con pros y contras.

  1. Defina en la definición de clase.No me gusta esto en absoluto porque creo que las definiciones de clases 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 modelo tienen el mismo nivel de complejidad.Este método también convierte la plantilla en una plantilla verdadera.

  2. Defina la plantilla en el mismo encabezado, pero fuera de la clase.Esta es mi forma preferida la mayoría de las veces.Mantiene ordenada la definición de su clase, la plantilla sigue siendo una plantilla verdadera.Sin embargo, requiere un nombre de plantilla completo, lo que puede resultar 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 las definiciones de su clase.

  3. Incluya el header.h y la implementación.CPP en su main.CPP.Creo que así es como se hace.No tendrás que preparar ninguna instanciación previa, se comportará como una verdadera plantilla.El problema que tengo es que no es natural.Normalmente no incluimos ni esperamos incluir archivos fuente.Supongo que como incluiste el archivo fuente, las funciones de la plantilla se pueden integrar.

  4. Este último método, que era el publicado, es definir las plantillas en un archivo fuente, tal como el número 3;pero en lugar de incluir el archivo fuente, creamos previamente una instancia 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 integrado, así que colóquelo en un archivo CPP.Y si conocemos instancias comunes y podemos predefinirlas.Esto nos ahorra tener que escribir básicamente lo mismo 5, 10 veces.Este método tiene la ventaja de mantener nuestro código como propietario.Pero no recomiendo incluir funciones pequeñas y utilizadas habitualmente en archivos CPP.Ya que esto reducirá el rendimiento de su biblioteca.

Tenga en cuenta que no conozco las consecuencias de un archivo obj inflado.

No hay nada de malo en el ejemplo que has 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 clases explícitas, la biblioteca Boost Concept Check Library (BCCL) puede ayudarlo a generar código de función de plantilla en archivos cpp.

¡Es 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 encima de cada función (template <typename T, ...>).Ahora, en lugar de incluir el archivo de encabezado en el archivo en línea, hace lo contrario.Incluir el archivo en línea después la declaración de su clase (#include "file.inl").

Realmente no sé por qué nadie ha mencionado esto.No veo inconvenientes inmediatos.

Tomemos un ejemplo, digamos que por alguna razón desea tener una clase de plantilla:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Si compila este código con Visual Studio, funciona de inmediato.gcc producirá un error en el vinculador (si se usa el mismo archivo de encabezado desde varios archivos .cpp):

error : multiple definition of `DemoT<int>::test()'; your.o: .../test_template.h:16: first defined here

Es posible mover la implementación al archivo .cpp, pero luego debes declarar una clase como esta:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

template <>
void DemoT<int>::test();

template <>
void DemoT<bool>::test();

// Instantiate parametrized template classes, implementation resides on .cpp side.
template class DemoT<bool>;
template class DemoT<int>;

Y luego .cpp se verá así:

//test_template.cpp:
#include "test_template.h"

template <>
void DemoT<int>::test()
{
    printf("int test (int)\n");
}


template <>
void DemoT<bool>::test()
{
    printf("int test (bool)\n");
}

Sin las dos últimas líneas en el archivo de encabezado, gcc funcionará bien, pero Visual Studio producirá un error:

 error LNK2019: unresolved external symbol "public: void __cdecl DemoT<int>::test(void)" (?test@?$DemoT@H@@QEAAXXZ) referenced in function

La sintaxis de la clase de plantilla es opcional en caso de que desee exponer la función a través de la exportación .dll, pero esto solo se aplica a la plataforma Windows, por lo que test_template.h podría verse así:

//test_template.h:
#pragma once
#include <cstdio>

template <class T>
class DemoT
{
public:
    void test()
    {
        printf("ok\n");
    }
};

#ifdef _WIN32
    #define DLL_EXPORT __declspec(dllexport) 
#else
    #define DLL_EXPORT
#endif

template <>
void DLL_EXPORT DemoT<int>::test();

template <>
void DLL_EXPORT DemoT<bool>::test();

con el archivo .cpp del ejemplo anterior.

Sin embargo, esto le da más dolor de cabeza al vinculador, por lo que se recomienda utilizar el ejemplo anterior si no exporta la función .dll.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top