codigokiddy
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>();
Principal.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 uso esta palabra clave solo para plantillas de clase como en la Figura 2?
Figura 2: plantillas de clase
TemplHeader.h
template<typename T>
class foo {
T f();
};
TemplCpp.cpp
template<typename T>
void foo<T>::f() {
//...
}
//explicit instantation
template class foo<int>;
Principal.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 uso extern template
? ¿Podemos usarlo solo para clases, o podemos usarlo también para funciones?
Además, la Figura 1 y la Figura 2 se pueden expandir a una solución donde las plantillas están en un solo archivo de encabezado. En ese caso, tenemos que usar el extern template
palabra clave para evitar varias instancias iguales. ¿Esto es solo para clases o funciones también?
Daniel
Solo debes usar extern template
forzar al compilador a no instanciar una plantilla cuando sabes que será instanciado en otro lugar. Se utiliza para reducir el tiempo de compilación y el tamaño del archivo de 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 objetos:
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 entre sí, uno void ReallyBigFunction<int>()
se descartará, lo que resultará en tiempo de compilación y tamaño de archivo de objeto desperdiciados.
Para no desperdiciar el tiempo de compilación y el tamaño del archivo de objeto, hay un extern
palabra clave que hace que el compilador no compile una función de plantilla. deberías usar esto si y solo si sabes se usa en el mismo binario en otro lugar.
Cambiando source2.cpp
a:
// source2.cpp
#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
ReallyBigFunction<int>();
}
Dará como resultado los siguientes archivos de objetos:
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 se vinculen, el segundo archivo de objeto solo usará el símbolo del primer archivo de objeto. No es necesario descartar ni desperdiciar tiempo de compilación ni tamaño de archivo de objeto.
Esto solo debe usarse dentro de un proyecto, como en momentos en que usa una plantilla como vector<int>
varias veces, debe usar extern
en todos los archivos de origen menos uno.
Esto también se aplica a las clases y funciones como una sola, e incluso a las funciones de miembros de plantilla.
-
@Dani: ¡la mejor explicación de plantillas externas que he leído hasta ahora!
– pietro
31 de agosto de 2012 a las 10:06
-
“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, como una función en línea). Se debe proporcionar una instanciación explícita en otra TU.
– Johannes Schaub – litb
10 de agosto de 2013 a las 12:44
-
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 suponer que la gran mayoría de los votantes en esta pregunta en realidad no implementaron este tipo de plantillas en múltiples unidades de compilación (como lo hice hoy)… Al menos para clang, la única forma segura es poner estas definiciones de plantilla en ¡su cabecera! ¡Cuidado!
– Steven Lu
17 de julio de 2014 a las 3:50
-
@ JohannesSchaub-litb, ¿podría elaborar un poco más o tal vez dar una mejor respuesta? No estoy seguro de haber entendido completamente sus objeciones.
– andreee
18 de agosto de 2017 a las 8:12
-
Además, puede colocar la “plantilla externa…” en “header.h” y crear un “header.cpp” que lo instancia explícitamente. La “plantilla externa” está definida para no hacer nada si está junto con una instanciación explícita. Esto al menos funciona en gcc 7.2.0 y clang 5.0.0, aún no lo he usado en VS.
– usuario1902689
8 de noviembre de 2017 a las 6:04
seje
Wikipedia tiene la mejor descripción
En C++03, el compilador debe instanciar una plantilla cada vez que se encuentra 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 plantillas externas, análogas a las declaraciones de datos externas.
C++03 tiene esta sintaxis para obligar al compilador a instanciar 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 un no estándar versión de esta característica 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 necesitaba compilarse en diferentes compiladores.
Mira la muestra en el pagina enlazada para ver que funciona más o menos de la misma manera. Puede esperar que el mensaje desaparezca con futuras versiones de MSVC, excepto, por supuesto, cuando use otro extensiones de compilador no estándar al mismo tiempo.
-
tnx por su respuesta sehe, entonces, ¿qué significa esto realmente 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 asegúrese de que la plantilla no se instancia más que a tiempo en VSC ++. compilador. gracias.
– codekiddy
15 de noviembre de 2011 a las 17:54
-
“… 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
(casi seguro que todos lo hacen),extern
no tiene efecto.– Andreas Haferburg
5 mayo 2015 a las 17:00
-
Sí, esta respuesta es engañosa. MSFT doc: “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 clase se consideran funciones en línea y siempre se instancian”. Desafortunadamente, todas las clases STL en VS (la última verificación fue en 2017) solo tienen métodos en línea.
– 0kgatos
21 de noviembre de 2017 a las 0:44
-
Eso se aplica a todas las declaraciones en línea, independientemente de dónde surjan, siempre @0kcats
– seje
21 de noviembre de 2017 a las 0:45
-
@sehe La referencia a Wiki con std::vector example y una referencia a MSVC en la misma respuesta hace que uno crea que podría haber algún beneficio al usar extern std::vector en MSVC, mientras que hasta ahora no lo hay. No estoy seguro si este es un requisito del estándar, tal vez otros compiladores tengan el mismo problema.
– 0kgatos
21 de noviembre de 2017 a las 8:04
Ciro Santilli OurBigBook.com
extern template
solo es necesario si la declaración de la plantilla 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, el extern template
no tiene efecto porque las definiciones de plantilla en los encabezados estaban incompletas:
void f();
: solo declaración, sin cuerpoclass foo
: declara métodof()
pero no tiene definición
Así que recomendaría simplemente quitar el extern template
definición en ese caso particular: solo necesita agregarlos 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>();
Principal.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
producción:
TemplCpp.o
0000000000000000 W void f<char>()
Main.o
U void f<char>()
y luego de man nm
vemos eso U
significa indefinido, por lo que la definición se quedó solo en TemplCpp
como se desee.
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 inteligente a reconstruir todos los incluidos, que podrían ser muchos archivos
- si queremos evitar la sobrecarga de archivos de objetos, no solo debemos hacer instancias explícitas (igual que con las declaraciones de encabezado incompletas), sino también agregar
extern template
en cada incluidor, que los programadores probablemente olvidarán hacer
Más ejemplos de estos se muestran en: Creación de instancias de plantillas explícitas: ¿cuándo se usa?
Dado que el tiempo de compilación es tan crítico en proyectos grandes, recomendaría encarecidamente declaraciones de plantillas incompletas, a menos que partes externas absolutamente necesiten reutilizar su código con sus propias clases personalizadas complejas.
Y en ese caso, primero intentaría usar el polimorfismo para evitar el problema del tiempo de compilación, y solo usaría plantillas si se pueden lograr mejoras de rendimiento notables.
Probado en Ubuntu 18.04.
damirlj
El problema conocido con las plantillas es el código hinchado, que es consecuencia de generar la definición de clase en todos y cada uno de los módulos que invocan la especialización de plantilla de clase. Para evitar esto, comenzando con C++0x, se podría usar la palabra clave externo frente a la especialización de plantilla de clase
#include <MyClass>
extern template class CMyClass<int>;
La instanciación explícita de la clase de plantilla debe ocurrir solo en una sola unidad de traducción, preferiblemente la que tiene definición de plantilla (MyClass.cpp)
template class CMyClass<int>;
template class CMyClass<float>;
Si ha usado extern para funciones anteriormente, se sigue exactamente la misma filosofía para las plantillas. si no es así, puede ser útil pasar por extern para funciones simples. Además, es posible que desee colocar los elementos externos en el archivo de encabezado e incluir el encabezado cuando lo necesite.
Este no es el uso correcto de las plantillas externas en absoluto… esto ni siquiera compila
– Daniel
15 de noviembre de 2011 a las 2:02
¿Podría tomarse un tiempo para formular la (única) pregunta más claramente? ¿Para qué estás publicando el código? No veo una pregunta relacionada con eso. También,
extern template class foo<int>();
parece un error.– seje
15 de noviembre de 2011 a las 2:03
@Dani> se compila muy bien en mi Visual Studio 2010, excepto el mensaje de advertencia: Advertencia 1 advertencia C4231: extensión no estándar utilizada: ‘externa’ antes de la instanciación explícita de la plantilla
– codekiddy
15 de noviembre de 2011 a las 2:36
La pregunta de @sehe 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(); parece un error”. no, no lo es, tengo un nuevo libro de C++ y ese es un ejemplo de mi libro.
– codekiddy
15 de noviembre de 2011 a las 2:39
@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 ‘identificación no calificada esperada’ cerca
()
en la línea externa. tanto su libro como su estudio visual están mal, intente usar un compilador compatible con más estándares como g ++ o clang y verá el problema.– Daniel
15 de noviembre de 2011 a las 2:45