参考:https://blog.csdn.net/tang05505622334/article/details/90478013
首先需要了解编译和连接的过程
void foo();
int main(){
foo();
return 0;
}
在上面这个例子中,foo函数是没有定义的,编译器在编译的时候不知道foo的定义在哪,因此对foo函数的调用会生成汇编码call foo
,这个foo只是一个符号,编译器期望在连接的时候能在别的.obj文件中找到foo函数的定义,然后把call foo
替换成call xxx
,若找不到foo的定义,则报错找不到foo定义。
对于模板,存在一个实例化的过程。所谓实例化过程,就是首先我们需要知道模板的定义,然后在我们使用模板生成某个特定类型的函数时(可以通过直接调用或者显式声明),编译器才会生成对应的二进制代码。若我们不知道模板的定义,则只生成一个函数调用的符号,期望连接时找到定义。实例化后,.obj文件就包含了对应的二进制代码。
//A.h
template<typename T>
void foo();
//B.cpp
#include"A.h"
int main(){
foo<int>();
return 0;
}
在上例中,A.h没有给出模板的定义,因此B.cpp生成的汇编码同样会生成call foo<int>
,这里foo<int>
也只是一个符号,具体的定义需要在别的.obj文件中找到。为了解决这个问题,我们可以创建一个文件C.cpp
//C.cpp
#include"A.h"
template<typename T>
void foo(){
//一些定义....
};
void foo<int>;
注意,我们还需要显式声明 void foo<int>
,否则无法触发void foo<int>
的实例化,从而无法生成对应的二进制代码。
可见,上述方法存在两个弊端:
1.需要额外维护一个文件;
2.需要对所有可能用到的类型显式初始化,这样生成的.obj文件会变得非常大,而且实际上只会用到其中几个函数,造成了空间上的浪费。
因此,最好的办法就是直接在头文件中给出定义。这样,当使用特定类型的模板时能够直接生成对应的二进制代码。而不是指望在连接的时候连接器会找到对应的定义(二进制代码)。
还有一点,当不同的cpp文件都使用了同一个模板,且具现化同一个类型的模板,比如void foo<int>
,那么连接的时候,会不会有名字冲突呢?不会的,这时候连接器就会做一些去重复的工作。