函数模板的多个定义

wil*_*ell 10 c++ linker templates

假设头文件定义了一个函数模板.现在假设#include这个头文件有两个实现文件,每个文件都调用了函数模板.在两个实现文件中,函数模板都使用相同的类型进行实例化.

// header.hh
template <typename T>
void f(const T& o)
{
    // ...
}

// impl1.cc
#include "header.hh"

void fimpl1()
{
    f(42);
}

// impl2.cc
#include "header.hh"

void fimpl2()
{
    f(24);
}
Run Code Online (Sandbox Code Playgroud)

人们可能会期望链接器会抱怨多个定义f().具体来说,如果f()不是模板那么确实如此.

  • 为什么链接器不会抱怨多个定义f()
  • 是否在标准中指定链接器必须优雅地处理这种情况?换句话说,我是否可以始终依靠与上述类似的程序进行编译和链接?
  • 如果链接器可以足够聪明地消除一组函数模板实例化的歧义,为什么它不能对常规函数执行相同的操作,因为它们与实例化的函数模板的情况相同?

cjm*_*cjm 6

Gnu C++ 编译器手册对此有很好的讨论。摘录:

C++ 模板是第一个需要比 UNIX 系统上通常发现的环境更多智能的语言功能。如果需要的话,编译器和链接器必须以某种方式确保每个模板实例在可执行文件中恰好出现一次,否则根本不出现。解决这个问题有两种基本方法,分别称为 Borland 模型和 Cfront 模型。

Borland模型

Borland C++ 通过将公共块的等效代码添加到其链接器来解决模板实例化问题;编译器在使用模板实例的每个翻译单元中发出模板实例,链接器将它们折叠在一起。这种模型的优点是链接器只需考虑目标文件本身;无需担心外部复杂性。这样做的缺点是,由于模板代码被重复编译,因此增加了编译时间。为此模型编写的代码往往在头文件中包含所有模板的定义,因为它们必须被视为已实例化。

C前端模型

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

当在 ELF 系统(例如 GNU/Linux 或 Solaris 2)或 Microsoft Windows 上与 GNU ld 版本 2.8 或更高版本一起使用时,G++ 支持 Borland 模型。在其他系统上,G++ 不实现这两种自动模型。


Fer*_*cio 5

为了支持C++,链接器足够聪明,可以识别出它们都是相同的功能,并抛弃除了一个以外的所有功能.

编辑:澄清:链接器不比较函数内容并确定它们是相同的.模板化函数标记为这样,链接器识别它们具有相同的签名.