usando plantilla externa (C ++ 11)

116

Figura 1: plantillas de funciones

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

Main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

¿Es esta la forma correcta de usar extern template, o utilizo esta palabra clave solo para plantillas de clase como en la Figura 2?

Figura 2: plantillas de clases

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

Main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Sé que es bueno poner todo esto en un archivo de encabezado, pero si instanciamos plantillas con los mismos parámetros en varios archivos, obtendremos varias definiciones iguales y el compilador las eliminará todas (excepto una) para evitar errores. ¿Cómo lo uso extern template? ¿Podemos usarlo solo para clases, o podemos usarlo también para funciones?

Además, la Figura 1 y la Figura 2 pueden expandirse a una solución donde las plantillas están en un solo archivo de encabezado. En ese caso, necesitamos usar la extern templatepalabra clave para evitar múltiples instancias iguales. ¿Es esto solo para clases o funciones también?

codekiddy
fuente
3
Este no es el uso correcto de plantillas externas en absoluto ... esto ni siquiera se compila
Dani
¿Podría tomarse un tiempo para formular la (única) pregunta con más claridad? ¿Para qué publicas el código? No veo una pregunta relacionada con eso. Además, extern template class foo<int>();parece un error.
sehe
@Dani> se compila bien en mi visual studio 2010, excepto el mensaje de advertencia: Advertencia 1 advertencia C4231: extensión no estándar utilizada: 'extern' antes de la instanciación explícita de la plantilla
codekiddy
2
@sela pregunta es muy simple: ¿cómo y cuándo usar la palabra clave de plantilla externa? (la plantilla externa es C ++ 0x new future por cierto) dijiste "Además, la clase de plantilla externa foo <int> (); parece un error". no, no lo es, tengo un nuevo libro de C ++ y ese es un ejemplo de mi libro.
codekiddy
1
@codekiddy: entonces Visual Studio es realmente estúpido ... en el segundo, el prototipo no coincide con la implementación, e incluso si lo soluciono, dice 'esperaba unqualified-id' cerca ()de la línea externa. tanto su libro como visual studio están equivocados, intente usar un compilador compatible con más estándares como g ++ o clang y verá el problema.
Dani

Respuestas:

181

Solo debe usar extern templatepara forzar al compilador a no instanciar una plantilla cuando sepa que se instanciará en otro lugar. Se utiliza para reducir el tiempo de compilación y el tamaño del archivo objeto.

Por ejemplo:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Esto dará como resultado los siguientes archivos de objeto:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Si ambos archivos están vinculados, uno void ReallyBigFunction<int>()se descartará, lo que resultará en una pérdida de tiempo de compilación y tamaño del archivo objeto.

Para no perder tiempo de compilación y tamaño de archivo de objeto, hay una externpalabra clave que hace que el compilador no compile una función de plantilla. Debe usar esto si y solo si sabe que se usa en el mismo binario en otro lugar.

Cambiar source2.cppa:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

Dará como resultado los siguientes archivos de objeto:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Cuando ambos estén vinculados, el segundo archivo de objeto solo usará el símbolo del primer archivo de objeto. No hay necesidad de descartar ni perder tiempo de compilación ni tamaño de archivo de objeto.

Esto solo debe usarse dentro de un proyecto, como en los momentos en que usa una plantilla como vector<int>varias veces, debe usarla externen todos los archivos fuente menos uno.

Esto también se aplica a las clases y la función como una, e incluso a las funciones de miembro de la plantilla.

Dani
fuente
2
@codekiddy: No tengo ni idea de lo que Visual Studio quiere decir con eso. Realmente debería usar un compilador más compatible si desea que la mayoría del código de C ++ 11 funcione correctamente.
Dani
4
@Dani: ¡la mejor explicación de plantillas externas que he leído hasta ahora!
Pietro
90
"si sabe que se usa en el mismo binario en otro lugar". Eso no es suficiente ni necesario. Su código está "mal formado, no se requiere diagnóstico". No se le permite confiar en una instanciación implícita de otra TU (el compilador puede optimizarla, al igual que una función en línea). Se debe proporcionar una instanciación explícita en otra TU.
Johannes Schaub - litb
32
Me gustaría señalar que esta respuesta probablemente sea incorrecta, y me mordió. Afortunadamente, el comentario de Johannes tuvo varios votos a favor y esta vez le presté más atención. Solo puedo asumir que la gran mayoría de los votantes en esta pregunta no implementaron este tipo de plantillas en múltiples unidades de compilación (como lo hice hoy) ... Al menos para el clang, la única forma infalible es poner estas definiciones de plantilla en ¡su cabecera! ¡Tenga cuidado!
Steven Lu
6
@ JohannesSchaub-litb, ¿podría explicar un poco más o quizás dar una mejor respuesta? No estoy seguro de haber entendido completamente sus objeciones.
andreee
48

Wikipedia tiene la mejor descripción

En C ++ 03, el compilador debe crear una instancia de una plantilla siempre que se encuentre una plantilla completamente especificada en una unidad de traducción. Si se crea una instancia de la plantilla con los mismos tipos en muchas unidades de traducción, esto puede aumentar drásticamente los tiempos de compilación. No hay forma de evitar esto en C ++ 03, por lo que C ++ 11 introdujo declaraciones de plantilla externas, análogas a las declaraciones de datos externas.

C ++ 03 tiene esta sintaxis para obligar al compilador a crear una instancia de una plantilla:

  template class std::vector<MyClass>;

C ++ 11 ahora proporciona esta sintaxis:

  extern template class std::vector<MyClass>;

que le dice al compilador que no cree una instancia de la plantilla en esta unidad de traducción.

La advertencia: nonstandard extension used...

Microsoft VC ++ solía tener una versión no estándar de esta función desde hace algunos años (en C ++ 03). El compilador advierte sobre eso para evitar problemas de portabilidad con el código que también debe compilarse en diferentes compiladores.

Mire la muestra en la página vinculada para ver que funciona aproximadamente de la misma manera. Puede esperar que el mensaje desaparezca con futuras versiones de MSVC, excepto, por supuesto, cuando se utilizan otras extensiones de compilador no estándar al mismo tiempo.

sehe
fuente
tnx para su respuesta, sehe, entonces lo que esto significa es que el futuro de la "plantilla externa" funciona completamente para VS 2010 y podemos simplemente ignorar la advertencia. (usando pragma para ignorar el mensaje, por ejemplo) y tenga en cuenta que la plantilla no se instancia más que a tiempo en VSC ++. compilador. Gracias.
codekiddy
4
"... que le dice al compilador que no cree una instancia de la plantilla en esta unidad de traducción ". No creo que esto sea cierto. Cualquier método definido en la definición de clase cuenta como en línea, por lo que si la implementación de STL usa métodos en línea para std::vector(bastante seguro que todos lo hacen), externno tiene ningún efecto.
Andreas Haferburg
Sí, esta respuesta es engañosa. Documento MSFT: "La palabra clave extern en la especialización solo se aplica a las funciones miembro definidas fuera del cuerpo de la clase. Las funciones definidas dentro de la declaración de la clase se consideran funciones en línea y siempre se instancian". Desafortunadamente, todas las clases STL en VS (la última vez que se verificó fue 2017) solo tienen métodos en línea.
0kcats
Eso se aplica a todas las declaraciones en línea independientemente de dónde surjan, siempre @ 0kcats
sehe
@sehe La referencia a Wiki con el ejemplo std :: vector y una referencia a MSVC en la misma respuesta hace que uno crea que podría haber algún beneficio en el uso de extern std :: vector en MSVC, mientras que no lo hay hasta ahora. No estoy seguro de si esto es un requisito del estándar, tal vez otros compiladores tengan el mismo problema.
0kcats
7

extern template solo es necesario si la plantilla de declaración está completa

Esto se insinuó en otras respuestas, pero no creo que se le haya dado suficiente énfasis.

Lo que esto significa es que en los ejemplos de OP, extern templateno tiene ningún efecto porque las definiciones de plantilla en los encabezados estaban incompletas:

  • void f();: solo declaración, sin cuerpo
  • class foo: declara el método f()pero no tiene definición

Por lo tanto, recomendaría simplemente eliminar la extern templatedefinición en ese caso particular: solo necesita agregarlas si las clases están completamente definidas.

Por ejemplo:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

Main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

compilar y ver símbolos con nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

salida:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

y luego man nmvemos que Usignifica indefinido, por lo que la definición se mantuvo solo TemplCppcomo se deseaba.

Todo esto se reduce a la compensación de declaraciones de encabezado completas:

  • ventajas:
    • permite que el código externo use nuestra plantilla con nuevos tipos
    • tenemos la opción de no agregar instancias explícitas si estamos de acuerdo con la hinchazón de objetos
  • desventajas:
    • al desarrollar esa clase, los cambios en la implementación del encabezado llevarán a los sistemas de compilación inteligentes a reconstruir todos los includers, que podrían ser muchos archivos
    • Si queremos evitar el exceso de archivos de objetos, no solo necesitamos crear instancias explícitas (lo mismo que con las declaraciones de encabezado incompletas), sino también agregar extern templatecada includer, lo que los programadores probablemente olvidarán hacer.

Se muestran más ejemplos de estos en: Creación de instancias de plantilla explícita : ¿cuándo se usa?

Dado que el tiempo de compilación es tan crítico en proyectos grandes, recomendaría encarecidamente declaraciones de plantilla incompletas, a menos que las partes externas necesiten absolutamente reutilizar su código con sus propias clases personalizadas complejas.

Y en ese caso, primero trataría de usar polimorfismo para evitar el problema del tiempo de compilación, y solo usaría plantillas si se pueden lograr mejoras notables en el rendimiento.

Probado en Ubuntu 18.04.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
4

El problema conocido con las plantillas es el exceso de código, que es consecuencia de generar la definición de clase en todos y cada uno de los módulos que invoca la especialización de la plantilla de clase. Para evitar esto, comenzando con C ++ 0x, se podría usar la palabra clave extern frente a la especialización de la plantilla de clase

#include <MyClass>
extern template class CMyClass<int>;

La instancia explícita de la clase de plantilla debe ocurrir solo en una sola unidad de traducción, preferiblemente la que tiene la definición de plantilla (MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;
Damirlj
fuente
0

Si ha utilizado extern para funciones antes, se sigue exactamente la misma filosofía para las plantillas. si no es así, puede ser útil utilizar extern para funciones simples. Además, es posible que desee poner el (los) externo (s) en el archivo de encabezado e incluir el encabezado cuando lo necesite.

qqqqq
fuente