在一般的 C++ 开发中,习惯将函数声明与实现放在不同的文件中,如声明放在 .h
文件,实现放在 .cpp
文件,并在其它地方引用时只包含 .h
文件。但对于 C++ 的模板,这是一个例外,它只能被写在一个文件中。
普通函数
// test.h
int sumInt(int a, int b);
// test.cpp
int sumInt(int a, int b) {
return a + b;
}
模板函数
// test.cpp
template<typename T>
T sum(T a, T b) {
return a + b;
}
要理解为什么这样,需要先了解 C++ 的编译、链接过程。首先要知道的是,每个 .cpp
文件会被独立编译为对应的 .obj
文件,这个文件是 .cpp
文件的二进制汇编版本。
但随着模块化设计的发展,可能会出现一个文件调用另一个文件函数的情况,由于独立编译的缘故,这些函数的地址不能被确定,因此生成的 CALL
指令跟随的是一个虚拟函数地址。
当整个编译过程结束后,开始链接流程。即将所有的 .obj
文件链接为对应操作系统的可执行文件,如 Windows 的 .exe
文件。在这个过程中,真实的函数地址才得以确定。
简单来说,编译期间不会检查函数是否实现,它只需要函数的声明。但在链接时,要处理函数间的调用关系,此时才会检查函数的具体实现。
由于 C++ 的模板原理是根据调用方参数类型,生成多个不同的实现。如
sum(1, 2);
sum(1.0, 2.0);
将生成如下两种实现
int sum(int a, int b) { return a + b; }
double sum(double a, double b) { return a + b; }
生成操作在编译阶段完成,若模板函数的声明与实现分离,且在其它地方使用时只引用了 .h
文件,则会由于无法找到对应的模板代码,无法生成出不同的实现。
由于编译阶段只检查函数有无声明,因此不会产生编译错误。但在链接时,会因无法找到与之匹配的实现而报错。
.hpp
文件;