使用外部模板 (C 11)

新手上路,请多包涵

图 1: 功能模板

模板头文件.h

 template<typename T>
void f();

模板Cpp.cpp

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

主文件

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

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

图 2: 类模板

模板头文件.h

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

模板Cpp.cpp

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

主文件

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

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

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

原文由 codekiddy 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 906
2 个回答

您应该只使用 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

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

原文由 Daniel 发布,翻译遵循 CC BY-SA 3.0 许可协议

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

这在其他答案中有所暗示,但我认为没有给予足够的重视。

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

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

所以我建议在这种特殊情况下只删除 extern template 定义:如果类已完全定义,您只需要添加它们。

例如:

模板头文件.h

 template<typename T>
void f();

模板Cpp.cpp

 template<typename T>
void f(){}

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

主文件

#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 中测试。

原文由 Ciro Santilli OurBigBook.com 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题