ChatGPT解决这个技术问题 Extra ChatGPT

使用外部模板 (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 关键字来避免多个相同的实例。这也仅适用于类或函数吗?

这根本不是 extern 模板的正确用法……这甚至无法编译
你能花点时间更清楚地表述(一个)问题吗?你发布代码是为了什么?我没有看到与此相关的问题。此外,extern template class foo<int>(); 似乎是一个错误。
@Dani> 它在我的 Visual Studio 2010 上编译得很好,除了警告消息:Warning 1 warning C4231: nonstandard extension used : 'extern' before template explicit instantiation
@sehe 问题很简单:如何以及何时使用 extern 模板关键字? (extern template is C++0x new future btw)你说“另外,extern template class foo(); 似乎是个错误。”不,不是,我有新的 C++ 书,这是我书中的例子。
@codekiddy:那么视觉工作室真的很愚蠢..在第二个原型中,原型与实现不匹配,即使我修复它,它在 extern 行的 () 附近显示“预期的 unqualified-id”。你的书和 Visual Studio 都错了,尝试使用更符合标准的编译器,如 g++ 或 clang,你会发现问题。

u
user1902689

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

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


@Dani:到目前为止我读过的对 extern 模板的最佳解释!
“如果你知道它在其他地方的同一个二进制文件中使用过。”。这既不够,也不需要。您的代码“格式错误,无需诊断”。不允许您依赖另一个 TU 的隐式实例化(允许编译器对其进行优化,就像内联函数一样)。必须在另一个 TU 中提供显式实例化。
我想指出这个答案可能是错误的,我被它咬了。幸运的是,约翰内斯的评论得到了很多赞成票,这一次我更加关注它。我只能假设这个问题的绝大多数选民实际上并没有在多个编译单元中实现这些类型的模板(就像我今天所做的那样)......至少对于 clang,唯一可靠的方法是将这些模板定义放入你的头!被警告!
@JohannesSchaub-litb,您能否详细说明一下,或者提供更好的答案?我不确定我是否完全理解你的反对意见。
此外,您可以将“extern 模板...”放在“header.h”中,并制作一个显式实例化它的“header.cpp”。 “外部模板”被定义为如果它与显式实例化一起不做任何事情。这至少适用于 gcc 7.2.0 和 clang 5.0.0,我还没有在 VS 中使用它。
a
akim

维基百科有 best description

在 C++03 中,只要在翻译单元中遇到完全指定的模板,编译器就必须实例化模板。如果模板在许多翻译单元中使用相同的类型进行实例化,则会显着增加编译时间。在 C++03 中没有办法阻止这种情况,因此 C++11 引入了外部模板声明,类似于外部数据声明。 C++03 有这样的语法来强制编译器实例化一个模板:模板类 std::vector; C++11 现在提供这种语法:extern template class std::vector;它告诉编译器不要在这个翻译单元中实例化模板。

警告:使用了非标准扩展...

Microsoft VC++ 曾经有一个 non-standard version of this feature 已经有几年了(在 C++03 中)。编译器会对此发出警告,以防止需要在不同编译器上编译的代码出现可移植性问题。

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


tnx 为您的回复 sehe,所以这实际上意味着“外部模板”未来完全适用于 VS 2010,我们可以忽略警告吗? (例如,使用 pragma 忽略该消息)并支持模板没有在 VSC++ 中按时实例化。编译器。谢谢。
“... 告诉编译器不要在这个翻译单元中实例化模板。”我不认为这是真的。类定义中定义的任何方法都算作内联方法,因此如果 STL 实现对 std::vector 使用内联方法(很确定所有方法都这样做),则 extern 无效。
是的,这个答案具有误导性。 MSFT 文档:“特化中的 extern 关键字仅适用于在类主体之外定义的成员函数。在类声明中定义的函数被视为内联函数,并且始终被实例化。”不幸的是,VS 中的所有 STL 类(最后一次检查是 2017 年)只有内联方法。
这适用于所有内联声明,无论它们出现在哪里,总是@0kcats
@sehe 使用 std::vector 示例对 Wiki 的引用以及在同一答案中对 MSVC 的引用使人们相信在 MSVC 中使用 extern std::vector 可能会有一些好处,而到目前为止还没有。不确定这是否是标准的要求,也许其他编译器也有同样的问题。
C
Ciro Santilli Путлер Капут 六四事

仅当模板声明完整时才需要extern template

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

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

void f();: 只是声明,没有正文

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

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

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

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

在 Ubuntu 18.04 中测试。


M
Marek R

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

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

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

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

q
qqqqq

如果您以前使用过 extern 作为函数,则模板遵循完全相同的理念。如果没有,通过 extern 进行简单的功能可能会有所帮助。此外,您可能希望将 extern(s) 放在头文件中,并在需要时包含头文件。