使用外部模板(C + + 11)

图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关键字来避免多个相同的瞬间。这也只适用于类或函数吗?

124983 次浏览

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

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

维基百科有 最佳描述

在 C + + 03中,每当完全指定的模板是 中的相同类型实例化的模板 许多翻译单位,这可以大大增加编译时间。没有办法 在 C + + 03中防止这种情况发生,所以 C + + 11引入了外部模板声明,类似于外部声明 数据声明。

C + + 03有这样的语法要求编译器实例化一个模板:

  template class std::vector<MyClass>;

C + + 11现在提供了以下语法:

  extern template class std::vector<MyClass>;

它告诉编译器不要在这个翻译单元中实例化模板。

警告: nonstandard extension used...

微软 VC + + 已经使用 此功能的非标准版本很多年了(在 C + + 03中)。编译器会警告这一点,以防止需要在不同编译器上编译的代码出现可移植性问题。

查看 译自: 美国《科学》杂志网站(http://support. microsoft.com/kb/168958)中的样本,可以看到它的工作原理大致相同。您可以预期消息将随着 MSVC 的未来版本而消失,当然,当同时使用 其他非标准编译器扩展时除外。

如果您以前使用过 extern 来实现函数,那么对于模板也遵循完全相同的原理。如果没有,那么通过 extern 获得简单的函数可能会有所帮助。另外,您可能希望将外部文件放在头文件中,并在需要时包含头文件。

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

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

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

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

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

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

这意味着在 OP 的示例中,extern template没有任何效果,因为标题上的模板定义是不完整的:

  • 只是声明,没有尸体
  • 声明方法 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上。

所有这些都可以归结为对完整头声明的权衡:

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

进一步的例子显示在: 显式模板实例化-何时使用?

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

在这种情况下,我将首先尝试使用多态性来避免构建时问题,并且只有在可以获得显著性能提高的情况下才使用模板。

在 Ubuntu 18.04中测试。