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

11 minutos de lectura

Almacenamiento de definiciones de funciones de plantilla de C en
Robar

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 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? Solo estoy usando este código con VS2008 en este momento, pero querré migrarlo a otros entornos.

  • No tenía idea de que esto fuera posible, ¡un truco interesante! Hubiera sido de gran ayuda para algunas tareas recientes saber esto, ¡salud!

    – xan

    22 de septiembre de 2008 a las 16:00

  • Lo que me pisotea es el uso de do como identificador :p

    – Quintín

    14/01/2015 a las 10:00

  • he hecho algo similar con gcc, pero sigo investigando

    – Nick

    25/09/2015 a las 16:01

  • Esto no es un “truco”, es una deceleración hacia adelante. Esto tiene un lugar en el estándar del idioma; entonces sí, está permitido en todos los compiladores conformes con el estándar.

    – Ahmet Ipkin

    07/03/2016 a las 14:33

  • ¿Qué pasa si tienes docenas de métodos? ¿Puedes simplemente hacer template class foo<int>;template class foo<std::string>; al final del archivo .cpp?

    – Ignorante

    11 de junio de 2018 a las 14:22

Almacenamiento de definiciones de funciones de plantilla de C en
Aaron N.Tubbs

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.

  • Solo para complementar la respuesta, el enlace al que se hace referencia responde la pregunta de manera positiva, es decir, es posible hacer lo que sugirió Rob y tener el código para que sea portátil.

    – ivotrón

    1 de mayo de 2011 a las 21:46

  • ¿Puedes publicar las partes relevantes en la respuesta misma? ¿Por qué se permiten tales referencias en SO? No tengo ni idea de qué buscar en este enlace, ya que ha cambiado mucho desde entonces.

    – Identificación

    16/08/2015 a las 21:57

1647590472 740 Almacenamiento de definiciones de funciones de plantilla de C en
espacio de nombres sid

Para otros en esta página que se preguntan cuál es la sintaxis correcta (como yo) para la especialización de plantilla explícita (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>;

  • ¿Quiere decir “para la especialización explícita de la plantilla CLASS”. En ese caso, ¿eso cubrirá todas las funciones que tiene la clase con plantilla?

    – 0x26res

    21 de febrero de 2013 a las 13:57

  • @Arthur parece que no, tengo algunos métodos de plantilla que permanecen en el encabezado y la mayoría de los otros métodos en cpp funcionan bien. Muy buena solución.

    – usuario1633272

    3 oct 2019 a las 15:29

  • En el caso del autor de la pregunta, tienen una plantilla de función, no una plantilla de clase.

    – usuario253751

    6 de marzo de 2020 a las 10:56

  • Por lo tanto, puede colocar varias plantillas de clase foo<...> en la parte inferior de cierto archivo, ¿verdad? Entonces, un archivo para definiciones para int, por ejemplo, Otro para float, si hay alguna diferencia, si no hay diferencias, ¿puede simplemente extraer la clase de plantilla foo debajo de int? ¿Estoy entendiendo esto bien?

    – Petko Kamenov

    9 de marzo de 2021 a las 12:23

  • Estoy completamente confundido acerca de su uso de nombre de tipo Y clase, aquí …

    – RichieHH

    6 sep 2021 a las 18:46

Este código está bien formado. Solo debe prestar atención a que la definición de la plantilla sea visible en el punto de instanciación. 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 miembro de datos estáticos de una plantilla de clase estará presente en cada unidad de traducción en la que se ejemplifique explícitamente.

  • Que hace no exportado ¿significar?

    – Dan Nissenbaum

    12 de junio de 2014 a las 19:29

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

    – Konrad Rodolfo

    12/06/2014 a las 19:32

  • Gracias. Pensé que todas las funciones son (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() }), entonces esto se vinculará con éxito. Si tengo razón, entonces la cita anterior parecería no aplicarse al caso típico… ¿me estoy equivocando en alguna parte?

    – Dan Nissenbaum

    12 de junio de 2014 a las 19:46

  • @Dan Trivial contraejemplo: inline funciones

    – Konrad Rodolfo

    12 de junio de 2014 a las 20:02

  • Las plantillas de función @Dan están implícitamente inline. La razón es que sin una ABI de C++ estandarizada es difícil/imposible definir el efecto que esto tendría de otro modo.

    – Konrad Rodolfo

    12/06/2014 a las 21:41


1647590472 174 Almacenamiento de definiciones de funciones de plantilla de C en
Cameron Tacklind

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, entre otros).

Sin embargo, 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

Agregue un tercer archivo que es el archivo de implementación/instanciación de la plantilla en sus fuentes.

lib/foo.hpp en/desde la biblioteca

#pragma once

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

lib/foo.cpp compilar este archivo directamente solo desperdicia tiempo de compilación

// Include guard here, just in case
#pragma once

#include "foo.hpp"

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

foo.MyType.cpp utilizando la biblioteca, creación de instancias de plantilla explícita de foo<MyType>

// Consider adding "anti-guard" to make sure it's not included in other translation units
#if __INCLUDE_LEVEL__
  #error "Don't include this file"
#endif

// Yes, we include the .cpp file
#include <lib/foo.cpp>
#include "MyType.hpp"

template class foo<MyType>;

Por supuesto, puede tener múltiples implementaciones en el tercer archivo. O puede querer varios archivos de implementación, uno para cada tipo (o conjunto de tipos) que le gustaría usar, por ejemplo.

Esta configuración debería reducir los tiempos de compilación, especialmente para el código de plantilla complicado que se usa mucho, porque no está recompilando el mismo archivo de encabezado en cada unidad de traducción. También permite una mejor detección de qué código se debe volver a compilar, por parte de los compiladores y los scripts de compilación, lo que reduce la carga de compilación incremental.

Ejemplos de uso

foo.MyType.hpp necesita saber sobre foo<MyType>interfaz pública de pero no .cpp fuentes

#pragma once

#include <lib/foo.hpp>
#include "MyType.hpp"

// Declare `temp`. Doesn't need to include `foo.cpp`
extern foo<MyType> temp;

examples.cpp puede hacer referencia a la declaración local pero tampoco vuelve a compilar foo<MyType>

#include "foo.MyType.hpp"

MyType instance;

// Define `temp`. Doesn't need to include `foo.cpp`
foo<MyType> temp;

void example_1() {
    // Use `temp`
    temp.bar(instance);
}

void example_2() {
    // Function local instance
    foo<MyType> temp2;

    // Use templated library function
    temp2.bar(instance);
}

error.cpp ejemplo que funcionaría con plantillas de encabezado puras pero no lo hace aquí

#include <lib/foo.hpp>

// Causes compilation errors at link time since we never had the explicit instantiation:
// template class foo<int>;
// GCC linker gives an error: "undefined reference to `foo<int>::bar()'"
foo<int> nonExplicitlyInstantiatedTemplate;
void linkerError()
{
    nonExplicitlyInstantiatedTemplate.bar();
}

Tenga en cuenta que la mayoría de los compiladores/linters/ayudantes de código no detectarán esto como un error, ya que no hay ningún error de acuerdo con el estándar C++. Pero cuando va a vincular esta unidad de traducción en un ejecutable completo, el vinculador no encontrará una versión definida de foo<int>.


Si la memoria no me falla, originalmente obtuve la idea de SO. Pero cuando escribí esta respuesta, no pude por mi vida encontrar esa SOA original. Hoy, creo que lo encontré: https://stackoverflow.com/a/495056/4612476

1647590473 648 Almacenamiento de definiciones de funciones de plantilla de C en
sombra de Luna

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

Almacenamiento de definiciones de funciones de plantilla de C en
Cassio Renan

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

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

  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 verdadera plantilla. Sin embargo, requiere un nombre de plantilla completo, lo 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 forma. También puede lograr esto creando un archivo .INL al final de sus definiciones de clase.

  3. Incluya header.h e deployment.CPP en su main.CPP. Creo que así es como se hace. No tendrá que preparar instancias previas, se comportará como una verdadera plantilla. El problema que tengo es que no es natural. Normalmente no incluimos y esperamos incluir archivos fuente. Supongo que como incluiste el archivo fuente, las funciones de la plantilla se pueden alinear.

  4. Este último método, que fue el publicado, está definiendo las plantillas en un archivo fuente, al igual que el número 3; pero en lugar de incluir el archivo fuente, instanciamos previamente las plantillas a las que necesitaremos. No tengo ningún problema con este método y es útil a veces. Tenemos un código grande, no puede beneficiarse de estar en línea, así que simplemente colóquelo en un archivo CPP. Y si conocemos instanciaciones comunes y podemos predefinirlas. Esto nos evita escribir básicamente lo mismo 5, 10 veces. Este método tiene la ventaja de mantener nuestro código propietario. Pero no recomiendo poner funciones diminutas y de uso regular en archivos CPP. Ya que esto reducirá el rendimiento de su biblioteca.

Tenga en cuenta que no estoy al tanto de las consecuencias de un archivo obj inflado.

1647590474 538 Almacenamiento de definiciones de funciones de plantilla de C en
Rojo XIII

Esto definitivamente 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 soliciten instanciación de plantilla, puede haber MUCHAS líneas en su archivo .cpp. Para remediar este problema, puede tener un TemplateClassInst.cpp en cada proyecto que use para tener un mayor control de 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 :).

  • ¿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 funciones de la plantilla), ¿no se garantiza que no violará la ODR ya que todas las definiciones son idénticas (incluso si se repiten)?

    – Dan Nissenbaum

    12 de junio de 2014 a las 19:36

  • Por favor, ¿qué es ODR?

    – no removible

    22 de junio de 2018 a las 11:45

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad