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


116

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


3
这根本不是extern模板的正确用法...这甚至都无法编译
Dani11年

您能否花一些时间更清楚地表达(一个)问题?您要发布什么代码?我没有看到与此相关的问题。另外,extern template class foo<int>();似乎是一个错误。
sehe

@Dani>它可以在我的Visual Studio 2010上正常编译,除了警告消息:警告1警告C4231:使用非标准扩展名:在模板显式实例化之前使用“ extern”
codekiddy

2
@sehe问题非常简单:如何以及何时使用extern template关键字?(extern模板是C ++ 0x新的未来)您曾说过“此外,extern模板类foo <int>();似乎是一个错误。” 不,不是,我有一本新的C ++书,那是我书中的例子。
codekiddy

1
@codekiddy:Visual Studio确实很愚蠢。在第二个中,原型与实现不匹配,即使我修复了它()在外部行附近说“ expected unqualified-id”的情况。您的书本和Visual Studio都错了,请尝试使用更符合标准的编译器(例如g ++或clang),您将看到问题。
达尼(Dani)

Answers:


181

仅当知道模板将在其他地方实例化时,才应使用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在除一个源文件之外的所有文件中使用。

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


2
@codekiddy:我不知道Visual Studio的含义。如果您希望大多数c ++ 11代码都能正常工作,则应该使用兼容的编译器。
达尼(Dani),

4
@Dani:到目前为止,我已经阅读了关于extern模板的最佳解释!
Pietro 2012年

90
“如果您知道它在其他地方的相同二进制文件中使用过”。这既不充分,也不是必需的。您的代码“格式错误,无需诊断”。不允许您依赖另一个TU的隐式实例化(允许编译器对其进行优化,就像内联函数一样)。必须在另一个TU中提供显式实例化。
Johannes Schaub-litb

32
我想指出,这个答案可能是错误的,我被它咬了。幸运的是,约翰内斯的评论得到了许多赞成,而我这次对此给予了更多关注。我只能假设,在这个问题上的绝大多数选民实际上并没有在多个编译单元中实现这些类型的模板(就像我今天所做的那样)...至少对于clang来说,唯一的放心方法就是将这些模板定义放入您的标题!被警告!
史蒂文·卢

6
@ JohannesSchaub-litb,您可以详细说明一下还是提供一个更好的答案?我不确定我是否完全理解您的反对意见。
andreee

48

维基百科具有最佳描述

在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 ++ 多年来已经具有此功能的非标准版本(在C ++ 03中)。编译器发出警告,以防止代码的可移植性问题也需要在不同的编译器上进行编译。

查看链接页面中的示例,以了解其工作原理大致相同。您可以期望该消息在将来的MSVC版本中消失,当然,同时使用其他非标准编译器扩展时除外。


用tnx来回复您的问题,所以这真是意味着“外部模板”的将来完全可以在VS 2010中使用,而我们可以忽略该警告吗?(例如,使用编译指示忽略消息),并支持模板在VSC ++中的实例化要比准时更多。编译器。谢谢。
codekiddy

4
“ ... 告诉编译器不要在此翻译单元中实例化模板。” 我不认为这是真的。在类定义中定义的任何方法都算作内联,因此,如果STL实现将内联方法用于std::vector(很确定所有方法都使用了),extern则无效。
安德烈亚斯·哈弗堡

是的,这个答案有误导性。MSFT doc:“专业化中的extern关键字仅适用于在类主体之外定义的成员函数。在类声明中定义的函数被视为内联函数,并且始终被实例化。” 不幸的是,VS中的所有STL类(上次检查是2017年)都只有内联方法。
0kcats

这也适用于所有内联声明,无论他们出现在哪里,总是@ 0kcats
sehe

@sehe在同一答案中对Wiki的引用带有std :: vector示例的引用和对MSVC的引用使人们相信,在MSVC中使用extern std :: vector可能会有一些好处,但到目前为止还没有。不知道这是否是标准的要求,也许其他编译器也有同样的问题。
0kcats

7

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所需的值。

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

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

进一步的示例显示在:显式模板实例化-什么时候使用它?

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

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

在Ubuntu 18.04中测试。


4

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

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

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

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

0

如果您以前使用过extern作为函数,则模板遵循完全相同的原理。如果不是这样,那么通过简单函数的extern可能会有所帮助。另外,您可能需要将extern(s)放在头文件中,并在需要时包括头。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.