函数模板的多个定义

Multiple definitions of a function template

本文关键字:定义 函数模板      更新时间:2023-10-16

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

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

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

博兰德模型

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

正面模型

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

当与 GNU ld 版本 2.8 或 一起使用时 后来在 ELF 系统上,例如 GNU/Linux 或 Solaris 2,或 Microsoft Windows,G++支持: 博兰德模型。在其他系统上,G++ 不实现任何自动模型。

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

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

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

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

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