如何组织编写模板程序

me:谈到了显式实例化

 

如何组织编写模板程序
 发表日期: 1/21/2003 12:28:58 PM
 发表人: Nemanja Trifunovic

前言
 常遇到询问使用模板到底是否容易的问题,我的回答是: 模板的使用是容易的,但组织编写却不容易 。看看我们几乎每天都能遇到的 模板类 吧,如STL, ATL, WTL,  以及 Boost 模板类 ,都能体会到这样的滋味:接口简单,操作复杂。

我在5 年前开始使用模板,那时我看到了 MFC 的容器类。直到去年我还没有必要自己编写 模板类 。可是在我需要自己编写 模板类 时,我首先遇到的事实却是 传统 编程方法 ( *.h 文件 声明 ,在*.cpp 文件中定义 ) 不能用于模板。于是我花费一些时间来了解问题所在及其解决方法。

本文对象是那些熟悉模板但还没有很多编写模板经验的程序员。本文只涉及 模板类 ,未涉及模板函数。但论述的原则对于二者是一样的。

问题的产生
 通过下例来说明问题。例如在array.h 文件中有 模板类 array

 然后在main.cpp 文件中的主函数中使用上述模板:


 

这时编译和运行都是正常的。程序先创建一个含有50 个整数的数组,然后设置数组的第一个元素值为 2 ,再读取第一个元素值,最后将指针指向数组起点。

但如果用传统编程方式来编写会发生什么事呢?我们来看看:

array.h 文件分裂成为 array.h array.cpp 二个文件 (main.cpp 保持不变 )


 


编译时会出现3 个错误。问题出来了:
 为什么错误都出现在第一个地方?
 为什么只有3 个链接出错? array.cpp 中有 4 个成员函数。

 要回答上面的问题,就要深入了解模板的实例化过程。

模板实例化
 程序员在使用 模板类 时最常犯的错误是将 模板类 视为某种数据类型。所谓类型参量化(parameterized types) 这样的术语导致了这种误解。模板当然不是数据类型,模板就是模板,恰如其名:

编译器使用模板,通过更换模板参数来创建数据类型。这个过程就是模板实例化(Instantiation)
 从 模板类 创建得到的类型称之为特例(specialization)
 模板实例化取决于编译器能够找到可用代码来创建特例( 称之为实例化要素,
 point of instantiation)
 要创建特例,编译器不但要看到模板的 声明 ,还要看到模板的定义。
 模板实例化过程是迟钝的,即只能用函数的定义来实现实例化。


 再回头看上面的例子,可以知道array 是一个模板, array<int, 50> 是一个模板实例  一个类型。从 array 创建 array<int, 50> 的过程就是实例化过程。实例化要素体现在 main.cpp 文件中。如果按照传统方式,编译器在 array.h 文件中看到了模板的 声明 ,但没有模板的定义,这样编译器就不能创建类型array<int, 50> 。但这时并不出错,因为编译器认为模板定义在其它文件中,就把问题留给链接程序处理。

现在,编译array.cpp 时会发生什么问题呢?编译器可以解析模板定义并检查语法,但不能生成成员函数的代码。它无法生成代码,因为要生成代码,需要知道模板参数,即需要一个类型,而不是模板本身。

这样,链接程序在main.cpp  或  array.cpp 中都找不到 array<int, 50> 的定义,于是报出无定义成员的错误。

至此,我们回答了第一个问题。但还有第二个问题,在array.cpp 中有 4 个成员函数,链接器为什么只报了 3 个错误?回答是:实例化的惰性导致这种现象。在 main.cpp 中还没有用上 operator[] ,编译器还没有实例化它的定义。

解决方法
 认识了问题,就能够解决问题:
 在实例化要素中让编译器看到模板定义。
 用另外的文件来显式地实例化类型,这样链接器就能看到该类型。
 使用export 关键字。

前二种方法通常称为包含模式,第三种方法则称为分离模式。

第一种方法意味着在使用模板的转换文件中不但要包含模板 声明 文件,还要包含模板定义文件。在上例中,就是第一个示例,在array.h 中用行内函数定义了所有的成员函数。或者在 main.cpp 文件中也包含进 array.cpp 文件。这样编译器就能看到模板的 声明 和定义,并由此生成array<int, 50> 实例。这样做的缺点是编译文件会变得很大,显然要降低编译和链接速度。

第二种方法,通过显式的模板实例化得到类型。最好将所有的显式实例化过程安放在另外的文件中。在本例中,可以创建一个新文件templateinstantiations.cpp
 // templateinstantiations.cpp               
 #include "array.cpp"

template class array <int, 50>; // 显式实例化
        
 array<int, 50>类型不是在 main.cpp 中产生,而是在 templateinstantiations.cpp 中产生。这样链接器就能够找到它的定义。用 这种方法,不会产生巨大的头文件,加快编译速度。而且头文件本身也显得更加 干净 和更具有可读性。但这个方法不能得到惰性实例化的好处,即它将显式地生 成所有的成员函数。另外还要维护 templateinstantiations.cpp 文件。

第三种方法是在模板定义中使用export 关键字,剩下的事就让编译器去自行处理了。当我在
 Stroustrup的书中读到 export 时,感到非常兴奋。但很快就发现 VC 6.0 不支持它,后来又发现根本没有编译器能够支持这个关键字 ( 第一个支持它的编译器要在 2002 年底才问世 ) 。自那以后,我阅读了不少关于 export  的文章,了解到它几乎不能解决用包含模式能够解决的问题。欲知更多的 export 关键字,建议读读 Herb Sutter 撰写的文章。

结论
 要开发模板库,就要知道 模板类 不是所谓的" 原始类型 " ,要用其它的编程思路。本文目的不是要吓唬那些想进行模板编程的程序员。恰恰相反,是要提醒他们避免犯下开始模板编程时都会出现的错误。

 

me:显式实例化完整代码如下

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值