【问题标题】:Multiple definitions of a function template函数模板的多个定义
【发布时间】:2010-09-19 03:14:09
【问题描述】:

假设一个头文件定义了一个函数模板。现在假设有两个实现文件#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() 的多个定义?
  • 标准中是否规定链接器必须优雅地处理这种情况?换句话说,我可以一直指望类似于上面的程序来编译和链接吗?
  • 如果链接器可以足够聪明地消除一组函数模板实例化的歧义,为什么它不能对常规函数做同样的事情,因为它们与实例化函数模板的情况相同?

【问题讨论】:

  • 标题具有误导性......“多个实例化”怎么样?还是“在单独的翻译单元中实例化”?
  • 我已经澄清了这个问题。这是一个相当广泛的问题,所以我把它分成更小的问题。也许我应该把这些分成几个 SO 问题?
  • 我在您的代码中没有看到 f() 的“多重定义”。

标签: c++ templates linker


【解决方案1】:

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

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

【讨论】:

  • 但是当 f() 不是模板时还不够聪明,无法识别这一点?
  • 不允许,模板除外。
  • 它也对内联函数执行此操作 - 如果它决定不内联它们。
  • @Mark Ransom 如果我理解正确,对于内联函数,如果编译器不打算内联它们,它会抱怨正常函数的多个定义?我说的对吗?
  • @John 没有。如果您在定义中包含inline 关键字,则表明您可以在多个翻译单元(源文件)中定义该函数,因此编译器会期望这一点并且不会抱怨。您仍然需要遵守单一定义规则并确保所有内联函数定义都相同 - 不需要检测它们是否不同。
【解决方案2】:

Gnu C++ 编译器手册有a good discussion of this。摘录:

C++ 模板是第一语言 需要更多智能的功能 从环境比一个通常 在 UNIX 系统上找到。不知何故 编译器和链接器必须确保 每个模板实例都会发生 如果它在可执行文件中恰好一次 是需要的,否则根本不需要。 对此有两种基本方法 问题,被称为 Borland 模型和 Cfront 模型。

Borland 模型

Borland C++ 解决了模板 通过添加实例化问题 等效于公共块的代码 他们的链接器;编译器发出 每个翻译中的模板实例 使用它们的单元和链接器 将它们折叠在一起。优势 这个模型的特点是链接器只有 必须考虑目标文件 他们自己;没有外部 需要担心的复杂性。这 缺点是编译时间 增加是因为模板代码 正在反复编译。代码 为这个模型写的往往 包括所有模板的定义 在头文件中,因为它们必须是 被实例化了。

Cfront 模型

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

当与 GNU ld 2.8 版或 稍后在 ELF 系统上,例如 GNU/Linux 或 Solaris 2,或更高版本 Microsoft Windows,G++ 支持 Borland 模型。在其他系统上,G++ 既不实现自动模型。

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-06-11
      • 1970-01-01
      • 2014-01-02
      • 1970-01-01
      • 1970-01-01
      • 2011-02-24
      • 2023-04-01
      • 2016-10-19
      相关资源
      最近更新 更多