类模板最常见的链接错误

一、问题引入

大多数  C/C++  程序员大致上都按照以下方式来组织他们的  non-template 程序代码: 
Classes  和其它类型被全体放置于头文件(header   files)。通常头文件的后缀名称(扩展名)  为.hpp(或   .H, .h, .hh, .hxx  等等)。 


全局变量和  non-inline  函数只在头文件中置入声明语句, 定义式则置于  .C  文件。 这里的  .C 文件是个统称,通常其后缀名称(扩展名)为.cpp(或.C, .c, .cc, .cxx  等等)。 


这种方式运作良好,程序能够轻易找到各个类型的定义,并避免一个变量或函数被多次重复定 义,于是链接器(linker)可以正常工作。 
 
如果牢记上述规则,很多  template 初学者就会犯一个常见错误。让我以下面这个例子加以说明。 
就像组织一般  non-template 程序代码一样,我们把  template 声明于某个头文件: 
// basics/myfirst.hpp 
#ifndef MYFIRST_HPP 
#define MYFIRST_HPP 
 
// template  声明语句 
template <typename T> 
void print_typeof (T const&); 
  
#endif // MYFIRST_HPP 
 


print_typeof()是一个简单的辅助函数,它打印参数的类型信息(type   information)。函数
的实际代码被置于一个.C 文件中: 
// basics/myfirst.cpp 
#include <iostream> 
#include <typeinfo> 
#include "myfirst.hpp" 
 
// template 的实作码/定义式 
template <typename T> 
void print_typeof (T const& x) { 
std::cout << typeid(x).name() << std::endl; 

 


这个例子使用  typeid  运算符,把参数的类型以字符串形式打印出来。 
 
此后,我们又在另外一个.C文件使用这个   template,并将   template  声明语句(的所在文件)以 #include  包含进来: 
// basics/myfirstmain.cpp 
#include "myfirst.hpp" 
 
//  使用含入之  template 
int main() { 
double ice = 3.0; 
print_typeof(ice); //  以  double  值调用  function template 

 
大多数 C++ 编译器都可以正确编译上述程序代码,然而大多数链接器会报告一个错误,表示无法  找到  print_typeof()函数的定义。 
 
错误的原因在于,function template print_typeof()的定义并没有被实例化。为了实例化一个  template,编译器必须知道「以哪一份定义式」以及「以哪些  template arguments」对它实例化。  不幸 的是先前这个例子中, 这两项信息被分置于两个分开编译的文件。 因此当编译器看 到对  print_typeof()的调用时,看不到其定义,无法以  double 类型来实例化  print_typeof()。  于是编译器假设这个   template  的定义位于其它某处,因而只生成一个对该定义的   reference,并将这个reference所指的定义式留给链接器 去决 议( resolve )。另一方 面,当编译 器处理 myfirst.cpp  时,它又察觉不到该以哪个自变量类型来实例化其定义式。 


二、解决问题思路

方法一:把  template 定义式放到其声明语句所在的头文件中。面对前例,我们可以在  myfirst.hpp  最后一行加入: #include "myfirst.cpp" 


方法二:在用到该template  的每一个 .C文件中  #include "myfirst.cpp"。


方法三:完全丢开  myfirst.cpp,把声明和定义全部放进  myfirst.hpp: 


// basics/myfirst2.hpp 
#ifndef MYFIRST_HPP 
#define MYFIRST_HPP 
 
#include <iostream> 
#include <typeinfo> 
 
// template  的声明语句 
template <typename T> 
void print_typeof (T const&); 
 
// template  的实作码/定义式 
template <typename T> 
void print_typeof (T const& x) { 
std::cout << typeid(x).name() << std::endl; 

 
#endif // MYFIRST_HPP 


这种   templates  组织法称为「置入式模型」(inclusion   model)。现在你会发现,上述程序可被正确编译、链接、执行。 
 
有一些需要注意的地方。最该引起注意的是:「含入   myfirst.hpp」的代价相当大。此处所谓  代价,不仅是指  template 定义式的体积增加,也包括含入 myfirst.hpp  时一并含入的其它(由  myfirst.hpp 含入的)文件,本例是<iostream> 和  <typeinfo>。你会发现,这可能导致成千上万行程序代码被 含 进来, 因为诸如<iostream> 这样的文件也是 以 此种方式来定 义 templates。 
 
这是一个很实际的问题。这显然会在编译大型程序时显著增加编译时间。很多现实世界中的程序会吃掉你数小时的编译+链接时间(关于这个我  们有多次经验,编译和链接一个程序,竟用掉悠悠数天工夫)。 
 
尽管存在这个问题,我们仍然强烈推荐:只要可能,你应该使用置入式模型(inclusion   model) 来组织你的   template  程序代码。虽然稍后还将审查另两种办法,但那些办法所带来工程缺陷看起  来远比时间占用问题更严重得多(不过它们也可能带来软件工程之外的其它好处)。 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值