原因
- 模板实例化机制:
- C++的模板是一种“编译期”的机制。编译器在遇到模板的使用(例如
template<typename T> void func(T t);
这样的模板声明,然后 func(10);
这样的使用)时,会根据实际的模板参数(这里是 int
)生成对应的函数实例代码。
- 当函数模板的声明和定义分离在不同文件时,比如声明在
header.h
中,定义在 source.cpp
中。在 main.cpp
中包含了 header.h
并使用了模板函数,编译器在编译 main.cpp
时,仅看到了模板声明,不知道模板函数的具体实现,所以不会生成模板函数的实例代码。
- 而在编译
source.cpp
时,由于没有模板函数的实际调用(模板参数没有确定),编译器也不会生成模板函数的实例代码。这样在链接阶段,链接器找不到模板函数实例的定义,就会报未定义符号错误。
- 链接器工作方式:
- 链接器的任务是将多个目标文件(
.obj
或 .o
文件)合并成一个可执行文件或库文件。它只关心目标文件中符号的定义和引用,对于模板函数,如果在目标文件中没有找到其具体实例的定义,就无法完成链接,导致未定义符号错误。
解决办法
- 将声明和定义放在同一个头文件中:
// template_func.h
template<typename T>
void func(T t) {
// 函数实现
std::cout << "The value is: " << t << std::endl;
}
// main.cpp
#include <iostream>
#include "template_func.h"
int main() {
func(10);
return 0;
}
- 这样在每个包含该头文件且使用了模板函数的编译单元中,编译器都能看到模板函数的完整定义,从而可以为每个使用的模板参数生成对应的实例代码。
- 显式实例化:
- 在定义模板函数的源文件中,显式实例化需要的模板函数实例。例如:
// source.cpp
#include <iostream>
template<typename T>
void func(T t) {
std::cout << "The value is: " << t << std::endl;
}
// 显式实例化针对int类型的模板函数
template void func<int>(int t);
- 在
main.cpp
中只需要包含模板函数的声明:
// main.cpp
#include <iostream>
// 模板函数声明
template<typename T>
void func(T t);
int main() {
func(10);
return 0;
}
- 这种方式下,
source.cpp
中的显式实例化语句告诉编译器为 int
类型生成 func
函数的实例代码,在链接阶段链接器就能找到该实例的定义,从而避免未定义符号错误。如果还需要其他类型的实例,比如 double
,则需要再添加 template void func<double>(double t);
这样的显式实例化语句。