【浅析】把实现放在头文件中的函数模板,为什么没有发生重定义错误

对于普通函数,典型的做法是在头文件中只放入声明,而定义写在别外的.cpp文件中。然后在要使用该函数的源文件中直接include头文件,即该函数的声明 即可。

但是对于模板函数,情况就不一样了。在要使用该模板函数的源文件中,既要include模版函数的声明,也要include模版函数的定义。因为只有在编译的时候,根据调用处的类型进行隐式实例化后,由编译器生成的函数才是真正的函数定义。

所以,这就涉及到了模板的第一种编译模式:

  • 完全包含编译。

//MyTemplate.h
template <typename T>
T add(T a,T b);

#include "MyTemplate.cpp"

//MyTemplate.cpp
template <typename T>
T add(T a,T b){
	return a+b;
}

//MyMain.cpp
#include "MyTemplate.h "
int main(){
	add(2,3);
	return 0;
}

//My2nd.cpp
#include "MyTemplate.h "
int My2nd(){
	add(2,3);
	return 0;
}

通过上面的代码我们可以看到,虽然模板的声明和定义表面上看似放在两个文件中,其实通过MyTemplate.h文件结尾处的#include "MyTemplate.cpp",我们就把该模板函数的声明和定义放在了一个文件中了。


同时,MyMain.cpp和My2nd.cpp中都用到了add函数模版,所以都要include "MyTemplate.h ",所以在两个cpp文件中都有一份add的定义,所以在隐式实例化以后,MyMain.cpp和My2nd.cpp这两个文件中,每个文件中都存在一个相同的函数定义:int add(int a,int b){return a+b;}

那么问题就来了。我们知道,如果对于普通函数,两个文件中存在相同的定义,在链接阶段,是会报重定义错误的。那为啥模板函数不会报错呢?

那就因为,对于这种重定义,在完全包含编译模式下,编译器会根据自己特有的一套机制,只保留一个int add(int a,int b)定义。简单的说,编译器也许会在不同的文件中产生两个相同的函数拷贝,但随后它会自动处理这种情况,只不过肯定要花费一些编译时间。这在实践中确实是一个问题,因为它增加了编译器在编译一个实际程序时所需的编译时间。理论上,对于怎么去除多余的相同函数定义,我们并不关心,因为这是编译器设计者应当关心的事情。

大多数时候一切都运转正常。然而,对于那些需要创建自己的库的大型项目,这个问题偶尔会显现出来。当然,那种情况是相当复杂的,没有万言书式的表述,是说不清楚的。

针对这种情况,以后再开篇博文再述吧。


  • 局部编译模式

简而言之,完全包含编译模式会使编译时间延长很多。为了避免完全编译模式的这种低效率,出现了局部编译模式。不过这是一种受到几乎所有知名编译器供应商的强烈抵制的模式。

局部编译模式声明和定义模板,和普通函数是一样的,只不过需要在模板定义的.cpp文件中加入export关键字。

//MyTemplate.h
template <typename T>
T add(T a,T b);

//#include "MyTemplate.cpp "//不需要了

//MyTemplate.cpp
export template <typename T> //export关键字
T add(T a,T b){
	return a+b;
}

//MyMain.cpp
#include "MyTemplate.h "
int main(){
	add(2,3);
	return 0;
}

//My2nd.cpp
#include "MyTemplate.h "
int My2nd(){
	add(2,3);
	return 0;
}

如果编译器不支持这种编译模式,那么不能使用export。export关键字只有在使用局部编译模式时才会用到,同时需要编译器支持局部编译模式。标准 C++ 制定了“模板分离编译模式(Separation Model)”及 export 关键字。然而由于 template 语义本身的特殊性使得使用 export 的时候,性能很差。编译器不得不像 .net 和 java 所做的那样,为模板实体生成一个“中间伪代码(IPC,intermediate pseudo- code)”,使得其它翻译单元在实例化时可找到定义体;而在遇到实例化时,根据指定的 typename 实参再将此 IPC 重新编译一遍,从而达到“分离编译”的目的。因此,该标准受到了几乎所有知名编译器供应商的强烈抵制。

一句话,模板的局部编译模式现在基本没人支持,GCC和VS都不支持。既然GCC都不支持,那就只能呵呵了。


  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值