使用extern模板(C++11)避免示例化

xvw2m8pv  于 2023-05-24  发布在  其他
关注(0)|答案(5)|浏览(168)

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

zhte4eai

zhte4eai1#

你应该只使用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
这也适用于类和函数,甚至模板成员函数。

9w11ddsr

9w11ddsr2#

维基百科有最好的描述
在C03中,每当在翻译单元中遇到完全指定的模板时,编译器必须示例化模板。如果在许多翻译单元中使用相同的类型示例化模板,则会显著增加编译时间。在C03中没有办法防止这种情况,所以C11引入了外部模板声明,类似于外部数据声明。
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**已经有几年了(在C03中)。编译器会对此发出警告,以防止需要在不同编译器上编译的代码出现可移植性问题。
查看**linked page**中的示例,可以看到它的工作方式大致相同。你可以期待这个消息在MSVC的未来版本中消失,当然,除了同时使用 * 其他 * 非标准编译器扩展时。

imzjd6km

imzjd6km3#

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,程序员可能会忘记这样做

这些方面的其他例子见:显式模板示例化-何时使用?
由于编译时间在大型项目中非常关键,我强烈建议不完整的模板声明,除非外部各方绝对需要在自己的复杂自定义类中重用您的代码。
在这种情况下,我会首先尝试使用多态性来避免构建时的问题,并且只在可以获得明显的性能收益时才使用模板。
在Ubuntu 18.04中测试。

slhcrj9b

slhcrj9b4#

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

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

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

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

bwntbbo35#

如果你以前使用过extern函数,那么模板也会遵循同样的原则。如果没有,通过extern获取简单的函数可能会有所帮助。此外,您可能希望将extern(s)放在头文件中,并在需要时包含头文件。

相关问题