11

假设一个头文件定义了一个函数模板。现在假设#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);
}

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

  • 为什么链接器不抱怨多个定义f()
  • 标准中是否规定链接器必须优雅地处理这种情况?换句话说,我可以总是指望类似于上面的程序来编译和链接吗?
  • 如果链接器可以足够聪明地消除一组函数模板实例化的歧义,为什么它不能对常规函数做同样的事情,因为它们与实例化函数模板的情况相同?
4

3 回答 3

5

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

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

于 2008-10-24T23:49:44.007 回答
5

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

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

Borland模型

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

前锋模型

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

在 GNU/Linux 或 Solaris 2 等 ELF 系统或 Microsoft Windows 上与 GNU ld 版本 2.8 或更高版本一起使用时,G++ 支持 Borland 模型。在其他系统上,G++ 都没有实现自动模型。

于 2008-10-25T05:30:38.500 回答
1

这或多或少只是模板的一种特殊情况。

编译器只生成实际使用的模板实例。由于它无法控制从其他源文件生成什么代码,它必须为每个文件生成一次模板代码,以确保完全生成该方法。

由于很难解决这个问题(标准有一个extern模板关键字,但 g++ 没有实现它)链接器只接受多个定义。

于 2008-10-24T23:57:45.643 回答