透彻理解c++模板包含模型(转)

来源:互联网 发布:淘宝客可以关闭吗 编辑:程序博客网 时间:2024/06/14 19:46

原文的地址是:http://blog.csdn.net/ixsea/article/details/6695496
观点
包含模型是C++模板源代码的一种组织方式,它鼓励将模板代码全部放在一个.h头文件中,这样可以避免莫名其妙的链接错误。
莫名其妙的链接错误
一般而言,程序员习惯将函数和类的声明放在.h文件、把它们的实现放在.cpp文件,这种多文件组织方式一直被倡导。一方面,这种分离使得代码逻辑清晰,想要了解程序用到哪些全局函数和类,只要查看.h文件就可以。如果把声明和实现都揉在一起,带来的麻烦可想而知,要在一堆乱糟糟的代码中寻找函数名、类名、成员名是一种折磨。另一方面,在构建动态链接库时,这种组织方式是必需的。因为动态链接库是二进制级别上的代码复用,它的一大优点就是具体的实现过程被隐藏起来,全部揉在一个.h文件中显然不符合要求。
然而不幸的是,当程序员仍然按照这种好的习惯编写模板代码时,却出现了问题。比如下面这个简单的例子:

// Bigger.h  template<typename T>  T Bigger(T,T);  //Bigger.cpp  #include"Bigger.h"  template<typename T>  T Bigger(T a,T b)  {      return a>b?a:b;  }  //main.cpp  #include"Bigger.h"  #include<iostream>  using namespace std;  int main()  {      cout<<Bigger(10,20)<<endl;      system("pause");      return 0;  }  

这几行代码很简单,分成了三个文件Bigger.h、Bigger.cpp以及main.cpp,分别对应模板函数Bigger的声明、定义和使用。看起来结构清晰,符合好的编码习惯,编译链接却得到这样的错误提示:

Error 1 error LNK2019: unresolved external symbol "int __cdecl Bigger<int>(int,int)" (??$Bigger@H@@YAHHH@Z) referenced in function _main E:\Codes\Chapter3Lab\includeModel\main.obj

意思是链接器找不到main.obj里Bigge函数的实现。这种看起来毫无道理的链接错误,也很好的体现了模板的实例化规则。
模板的实例化规则
对于模板函数来说,只有被调用的模板函数才被实例化,这里的被调用并不要求它必须被main函数调用。某个普通函数调用了模板函数,该模板函数就将对应产生一个实例,而调用它的普通函数可能并不被main调用,也即有可能并不被执行。
模板类也有类型的实例化规则,特别的是即使显式实例化了类模板,类模板的成员函数也未必被实例化,这是模板类的“不完全”实例化规则.
链接错误的解释
了解了模板的实例化规则,就可以对上面的链接错误做出解释了。main.cpp中调用了Bigger(10,20),按理说这将引起模板函数Bigger(T,T)被实例化为普通函数,然而在main.cpp所属的翻译单元里并没有Bigger(T,T)的实现,对main.cpp所属的翻译单元来说,Bigger(T,T)的实现是不可见的。因此,由main.cpp所属翻译单元编译得到main.obj时,编译器假设Bigger(int,int)在其它翻译单元中。
Bigger.cpp虽然有Bigger(T,T)的实现,但是由于在Bigger.cpp所属翻译单元中Bigger并没有被调用,因此Bigger.cpp就没有义务对模板函数Bigger(T,T)进行实例化,于是由它产生的Bigger.obj中也找不到的Bigger(int,int)。
本文前述例子中的链接错误信息正是表达的这个意思。
链接错误的进一步探讨
既然是因为Bigger.cpp没有义务对Bigger(T,T)进行实例化,那么在Bigger.cpp中增加一个调用Bigger(int,int)函数的普通函数是否就可以了呢?在Bigger.cpp文件中添几行代码,如下所示:

//Bigger.cpp  #include"Bigger.h"  template<typename T>  T Bigger(T a,T b)  {      return a>b?a:b;  }  void g()  //增加一个调用Bigger<int>(int,int)的普通函数g()  {      Bigger(1,2);  }  

编译、链接成功,允许结果正确,进一步验证了上述观点。
解决方法 - 包含模型
本文列出的例子很简单,规模小,所以按照模板的实例化规则,“人为”地介入到模板函数的实例化过程中并让程序成功运行。但是,在规模较大的程序里,想要人为介入加以控制几乎是不可能的,应该使用C++推荐的包含模型。
具体做法并不复杂:把模板的声明和定义放在一个.h文件中,凡是用到该模板的.cpp文件包含它所在的.h文件就可以了。上面的例子使用包含模型改写,最终是代码是这样的:

// Bigger.h  template<typename T>  T Bigger(T a,T b)  {      return a>b?a:b;  }  //main.cpp  #include"Bigger.h"  #include<iostream>  using namespace std;  int main()  {      cout<<Bigger(10,20)<<endl;      system("pause");      return 0;  }  

不过仍然有一个问题值得思考:当多个.cpp文件同时包含Bigger.h时,就有可能产生多份相同类型的实例化,这样是否会造成最终生成的.exe文件变得庞大?这个问题理论上是存在的,不过现在大多数编译器都对此作了一定的优化,一个模板的相同类型有多份实例化体时,编译器最终只保留一个,这样就避免了“代码膨胀”的问题。

原创粉丝点击