模板定义为什么要写在头文件

参考: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>,那么连接的时候,会不会有名字冲突呢?不会的,这时候连接器就会做一些去重复的工作。

发布了141 篇原创文章 · 获赞 27 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/a13602955218/article/details/105332057