隐式模板实例化何时发生

When does implicit template instantiation occur?

本文关键字:何时发 实例化      更新时间:2023-10-16

我想知道在以下情况下隐式模板实例化何时/何处发生。

// temp.h
template <typename T>
struct A {
    T value;
}
// foo.h
#include "temp.h"
void foo();
// foo.cpp
#include "foo.h"
void foo() { A<int> _foo; }
// bar.h
#include "temp.h"
void bar();
// bar.cpp
#include "bar.h"
void bar() { A<int> _bar; }
// main.cpp
#include "foo.h"
#include "bar.h"
int main() { foo(); bar(); return 0; }

我认为它发生在foo()被调用时,因为它是A<int>的第一次使用,所以A<int>是在foo.o实现的。
并且,当bar()被调用时,它在foo.o链接到A<int>

我说的对吗?或者实例化发生两次?

标准没有说明编译器应该如何隐式实例化模板。

我不确定其他编译器,这就是g++如何处理它,从7.5模板在哪里?:

在某种程度上,如果需要,编译器和链接器必须确保每个模板实例在可执行文件中只出现一次,否则根本不会出现。有两种基本方法来解决这个问题,它们被称为Borland模型和Cfront模型。
  • 宝蓝模型:

Borland c++解决了模板实例化问题,方法是在它们的链接器中添加与通用块等价的代码;编译器在每个使用模板实例的翻译单元中发出模板实例,链接器将它们折叠在一起。这个模型的优点是链接器只需要考虑目标文件本身;不需要担心外部的复杂性。缺点是编译时间增加,因为模板代码被反复编译。为这个模型编写的代码倾向于在头文件中包含所有模板的定义,因为它们必须被实例化。

  • Cfront模型:

c++翻译器Cfront通过创建模板存储库的概念解决了模板实例化问题,模板存储库是一个自动维护的存储模板实例的地方。存储库的一个更现代的版本是这样工作的:在构建单个对象文件时,编译器将在存储库中放置遇到的任何模板定义和实例化。在链接时,链接包装器将在存储库中添加对象,并编译之前未发出的任何所需实例。该模型的优点是更优的编译速度和使用系统链接器的能力;为了实现Borland模型,编译器供应商还需要替换链接器。缺点是大大增加了复杂性,因此有可能出错;对于某些代码,这可以是透明的,但在实践中,在一个目录中构建多个程序和在多个目录中构建一个程序可能非常困难。为该模型编写的代码倾向于将非内联成员模板的定义分离到一个单独的文件中,该文件应单独编译。

这是g++如何实现它的,我强调:

g++在链接器支持的目标上实现Borland模型,包括ELF目标(如GNU/Linux)、Mac OS X和Microsoft Windows。否则g++不实现自动模型。

也就是说:在g++中,每个翻译单元都有自己的实例化。那一页又提到了:

…,但是每个翻译单元都包含它使用的每个模板的实例。重复的实例将被链接器丢弃,但在大型程序中,这可能导致目标文件或共享库中出现不可接受的代码重复量。

你的选项来避免它(当然第一个和最后一个选项是明确的):

  1. 可以通过在一个对象文件中定义显式实例化来避免模板的重复实例,并通过使用extern模板语法使用显式实例化声明来防止编译器在任何其他对象文件中进行隐式实例化。

  2. 使用-frepo编译使用模板的代码。编译器生成扩展名为.rpo的文件,列出了在相应的对象文件中可以在那里实例化的所有模板实例化;链接包装器collect2然后更新.rpo文件,告诉编译器在哪里放置这些实例化,并重建任何受影响的目标文件。在第一次传递之后,链接时间开销可以忽略不计,因为编译器继续将实例化放在相同的文件中。

  3. -fno-implicit-templates编译你的代码来禁用模板实例的隐式生成,并显式地实例化所有你使用的模板实例。

根据GNU c++编译器文档(https://gcc.gnu.org/onlinedocs/gcc/Template-Instantiation.html)"每个翻译单元包含它所使用的每个模板的实例"。所以除非你没有用的_foo和_bar对象优化后,两个目标文件都应该有复制实例。然后,"重复的实例将被链接器丢弃",这通常意味着链接的顺序决定使用哪个实例。但是两种顺序的最终结果都是一样的