在多个文件中使用函数模板

考虑下述程序,它无法正确编译/链接

main.cpp

#include <iostream>

template <typename T>
T addOne(T x); // 函数模板前向声明

int main()
{
    std::cout << addOne(1)   << '\n';
    std::cout << addOne(2.3) << '\n';
    return 0;
}

add.cpp

template <typename T>
T addOne(T x) // 函数模板定义
{
    return x + 1;
}

addOne 是普通函数,上述写法可行:

  • 编译器在 main.cpp 里看到前向声明即可;
  • 链接器会把 main.cpp 中对 addOne() 的调用连接到 add.cpp 中的定义。

然而,模板函数并不遵循这一流程,结果得到链接错误:

error LNK2019: unresolved external symbol "int __cdecl addOne<int>(...)"
error LNK2019: unresolved external symbol "double __cdecl addOne<double>(...)"

原因如下:

  1. main.cpp 中,编译器只看到前向声明,无法看到模板定义,因此不能实例化 addOne<int>addOne<double>
  2. add.cpp 中,虽然有模板定义,但没有任何调用,编译器也不会实例化任何版本;
  3. 链接阶段找不到已实例化的函数,于是报错。

(补充:即使在 add.cpp 里手动实例化所需版本,也只是权宜之计,代码脆弱,不应采用。)

解决之道——将模板完整定义放入头文件

add.h

#ifndef ADD_H
#define ADD_H

template <typename T>
T addOne(T x)
{
    return x + 1;
}

#endif

main.cpp

#include "add.h"  // 直接包含模板定义
#include <iostream>

int main()
{
    std::cout << addOne(1)   << '\n'; // 实例化 addOne<int>
    std::cout << addOne(2.3) << '\n'; // 实例化 addOne<double>
    return 0;
}
  • 预处理器把 add.h 内容复制到任何包含它的源文件;
  • 编译器在每个翻译单元都能看到完整定义,按需实例化所需版本;
  • 由于模板定义不受“一次定义规则”(ODR) 的限制,且由模板隐式实例化出的函数均为 inline,因此不会出现重复定义问题。

关键洞察

  • 模板定义可安全地包含在多个源文件;
  • 由模板实例化得到的函数自动为 inline,可在多个翻译单元中拥有相同定义。

最佳实践

凡需在多个文件中使用的模板,一律将其完整定义放入头文件,并在需要的地方 #include 该头文件。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"