11-10 在多个文件中使用函数模板
考虑以下无法正常运行的程序:
main.cpp:
#include <iostream>
template <typename T>
T addOne(T x); // function template forward declaration
int main()
{
std::cout << addOne(1) << '\n';
std::cout << addOne(2.3) << '\n';
return 0;
}
add.cpp:
template <typename T>
T addOne(T x) // function template definition
{
return x + 1;
}
若addOne为非模板函数,程序将正常运行:编译器在main.cpp中仅需addOne的前向声明即可满足要求,链接器会将main.cpp中对addOne()的调用与add.cpp中的函数定义正确关联。
但由于addOne是模板函数,程序运行失败并引发链接器错误:

在 main.cpp 中,我们调用了 addOne
当编译器处理 add.cpp 时,会看到函数模板 addOne 的定义。但由于 add.cpp 中未使用该模板,编译器不会进行实例化。最终导致链接器无法将 main.cpp 中对 addOne
顺带一提……
若add.cpp中实例化了这些函数,程序编译链接本可顺利完成。但此类解决方案存在脆弱性,应予以避免:若add.cpp后续修改导致函数不再实例化,程序将再次链接失败。或者当 main.cpp 调用 add.cpp 未实例化的 addOne 不同版本(如 addOne)时,同样会遇到相同问题。
解决此问题的最常规方法是将所有模板代码移至头文件 (.h) 而非源文件 (.cpp):
add.h:
#ifndef ADD_H
#define ADD_H
template <typename T>
T addOne(T x) // function template definition
{
return x + 1;
}
#endif
main.cpp:
#include "add.h" // import the function template definition
#include <iostream>
int main()
{
std::cout << addOne(1) << '\n';
std::cout << addOne(2.3) << '\n';
return 0;
}

这样,需要使用该模板的文件即可通过#include包含相关头文件,预处理器会将模板定义复制到源文件中。编译器随后就能实例化所需的函数。
您可能疑惑:为何这不会违反单定义规则(ODR)?ODR规定类型、模板、内联函数及内联变量可在不同文件中存在完全相同的定义。因此模板定义被复制到多个文件中并无问题(前提是每个定义完全一致)。
相关内容
我们在第2.7课——前向声明与定义中探讨过ODR。
那么实例化函数本身呢?若函数在多个文件中被实例化,如何避免违反ODR?答案在于:从模板隐式实例化的函数具有隐式内联属性。正如你所知,内联函数可在多个文件中定义,前提是每个文件的定义完全一致。
关键洞见
模板定义不受“单定义规则”中“每个程序仅允许一个定义”的限制,因此将相同的模板定义#include到多个源文件中并不构成问题。而从函数模板隐式实例化的函数具有隐式内联属性,只要每个定义完全一致,即可在多个文件中定义。
模板本身并非内联,因为内联概念仅适用于变量和函数。
以下是函数模板置于头文件中的另一个示例,使其可被多个源文件包含:
max.h:
#ifndef MAX_H
#define MAX_H
template <typename T>
T max(T x, T y)
{
return (x < y) ? y : x;
}
#endif
foo.cpp:
#include "max.h" // import template definition for max<T>(T, T)
#include <iostream>
void foo()
{
std::cout << max(3, 2) << '\n';
}
main.cpp:
#include "max.h" // import template definition for max<T>(T, T)
#include <iostream>
void foo(); // forward declaration for function foo
int main()
{
std::cout << max(3, 5) << '\n';
foo();
return 0;
}
在上例中,main.cpp 和 foo.cpp 都包含了 #include “max.h”,因此两个文件中的代码都能使用 max

最佳实践
需要在多个文件中使用的模板应定义在头文件中,然后在需要时通过 #include 引入。这能让编译器看到完整的模板定义,并在需要时实例化模板。

浙公网安备 33010602011771号