使用extern模板(C ++ 11)

时间:2011-11-15 01:58:50

标签: c++ templates c++11 extern

图1:功能模板

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;
}

这是使用extern template的正确方法,还是仅将此关键字用于类模板,如图2所示?

图2:类模板

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;
}

我知道将所有这些放在一个头文件中是好的,但是如果我们在多个文件中实例化具有相同参数的模板,那么我们会得到多个相同的定义,编译器会将它们全部删除(除了一个)以避免错误。我如何使用extern template?我们可以只将它用于类,还是可以将它用于函数?

此外,图1和图2可以扩展为模板位于单个头文件中的解决方案。在这种情况下,我们需要使用extern template关键字来避免多个相同的即时消息。这仅适用于课程或职能吗?

5 个答案:

答案 0 :(得分:157)

你知道它将在其他地方实例化时,你应该只使用extern template强制编译器实例化模板。它用于减少编译时间和目标文件大小。

例如:

// 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>();
}

这将产生以下目标文件:

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

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

如果两个文件都链接在一起,则会丢弃一个void ReallyBigFunction<int>(),导致编译时间和目标文件大小浪费。

为了不浪费编译时间和目标文件大小,有一个extern关键字使编译器无法编译模板函数。你应该使用这个当且仅当你知道它在其他地方的同一个二进制文件中使用时。

source2.cpp更改为:

// source2.cpp

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

将导致以下目标文件:

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

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

当两者都链接在一起时,第二个目标文件将只使用第一个目标文件中的符号。不需要丢弃,也不需要浪费编译时间和目标文件大小。

这应仅在项目中使用,例如,当您多次使用vector<int>这样的模板时,除了一个源文件外,您应该使用extern

这也适用于类和函数作为一个,甚至模板成员函数。

答案 1 :(得分:42)

维基百科有best description

  

在C ++ 03中,只要完全指定的模板,编译器就必须实例化模板   在翻译单位遇到。如果模板使用相同的类型进行实例化   许多翻译单元,这可以大大增加编译时间。没有办法   在C ++ 03中防止这种情况,所以C ++ 11引入了extern模板声明,类似于extern   数据声明。

     

C ++ 03具有这种语法,迫使编译器实例化模板:

  template class std::vector<MyClass>;
     

C ++ 11现在提供了这种语法:

  extern template class std::vector<MyClass>;
     

告诉编译器不要在此转换单元中实例化模板。

警告:nonstandard extension used...

Microsoft VC ++曾经有过几年的 non-standard version of this feature (在C ++ 03中)。编译器警告这一点,以防止需要在不同编译器上编译的代码的可移植性问题。

查看 linked page 中的示例,看它的工作方式大致相同。您可以期望该消息随着未来版本的MSVC而消失,当然除非同时使用其他非标准编译器扩展。

答案 2 :(得分:6)

extern template仅在模板声明完成时才需要

其他答案暗示了这一点,但是我认为没有给予足够的重视。

这意味着在OP的示例中,extern template无效,因为标头上的模板定义不完整:

  • void f();:只是声明,没有正文
  • class foo:声明方法f(),但没有定义

因此,在这种特殊情况下,我建议仅删除extern template定义:仅在完全定义了类的情况下才需要添加它们。

例如:

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;
}

使用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

输出:

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

然后从man nm中我们看到U的意思是未定义,因此该定义确实只保留在TemplCpp上。

所有这些归结为完整标头声明的权衡:

  • 优势:
    • 允许外部代码将我们的模板用于新类型
    • 如果对象膨胀,我们可以选择不添加显式实例化
  • 缺点:
    • 在开发该类时,标头实现的更改将导致智能构建系统重建所有包含者,其中可能包含许多文件
    • 如果要避免目标文件膨胀,我们不仅需要执行显式实例化(与不完整的标头声明相同),还需要在每个includer上添加extern template,程序员可能会忘记这样做

其中的更多示例显示在:Explicit template instantiation - when is it used?

由于在大型项目中编译时间非常重要,因此,我强烈建议您使用不完整的模板声明,除非外部各方绝对需要通过自己的复杂自定义类重用您的代码。

在那种情况下,我将首先尝试使用多态性来避免构建时间问题,并且仅在可以显着提高性能的情况下才使用模板。

在Ubuntu 18.04中测试。

答案 3 :(得分:4)

模板的已知问题是代码膨胀,这是在调用类模板特化的每个模块中生成类定义的结果。为了防止这种情况,从C ++ 0x开始,可以在类模板专门化前面使用关键字 extern

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

模板类的显式实例应仅在单个翻译单元中发生,最好是具有模板定义的单元(MyClass.cpp)

template class CMyClass<int>;
template class CMyClass<float>;

答案 4 :(得分:0)

如果您之前使用过extern函数,则模板遵循完全相同的原则。如果没有,虽然简单的功能可能会有所帮助。此外,您可能希望将extern放在头文件中,并在需要时包含标题。