9.7.1 自动的实例化器(1)
C++(www.cppentry.com)的ANSI/ISO定义要求C++(www.cppentry.com)开发环境能够自动实例化被创建程序使用的所有模板特化(参照前言中特化的定义)。假设我们的程序库提供了下面两个模板:
- List.h:
- template<class T>
- class List {
- public:
- void insert(const T& t);
- //...
- };
- Hashtable.h:
- template<class T>
- class Hashtable{
- public:
- Hashtable();
- void insert(const T& t);
- private:
- List<T> the_table[1024];
- //...
- };
一个List<T>是一个元素类型为T的链表。Hashtable<T>是一个固定大小的、元素类型为T的哈希表,这个哈希表使用了开放链(就是说,当有多于一个的值散列到同一个位置时,就把这些值都保存在以这个位置为头节点的链表中)。Hashtable的实现还使用了一个固定大小的List数组,即the_table。
假设我们程序库的用户创建了一个Hashtable<Widget>,其中Widget是用户定义的一个类;那么自动的实例化器必须能够检测到包含程序使用了Hashtable<Widget>和List<Widget>;这个实例化器并且还能够实例化这两个类所需要的所有对象。
有几种方法可以用来设计自动的实例化器,下面两个小块将分别讨论两种常用的设计方案(另一种设计方案在练习9.9讨论)。
1.编译期实例化器
在每个翻译单元里,编译期实例化器将会直接或者间接地实例化这个翻译单元使用的所有模板特化。编译期实例化器为了可以顺利执行实例化,还会要求用户#include所有需要的头文件(这一点与链接期实例化器有区别,我们将在下一小节讨论链接期实例化器)。
假设Hashtable::insert调用了List::insert:
- template<classT>
- void Hashtable<T>::insert(const T& t) {
- int i;
- //...
- the_table[i].insert(t); //调用List::insert
- //...
- }
如果用户的翻译单元调用了Hashtable<Widget>::insert,那么编译期的模板instantiator(实例化器)将会在这个翻译单元的目标代码中产生:Hashtable<Widget>::insert的定义、List<Widget>::insert的定义和其他任何这两个函数直接或者间接使用的特化。
当然,在每个翻译单元的目标文件中,产生所有这个翻译单元直接或者间接使用的特化,将会导致一个这样的结果:在几个需要链接成一个程序的翻译单元目标文件中,将会有重复的定义存在。当这个程序被链接的时候,只有一份重复定义的拷贝会被合并到可执行程序中(即其他的重复定义将不起作用)。因此,编译期实例化器往往和能够去除重复定义的特殊链接器配合使用。
编译期实例化器最大的缺点就是效率不高。如果在n个翻译单元中都存在某个相同的特化,那么就需要编译n次,并产生n份拷贝。对于那些经常使用很多模板的大型程序开发者而言,这些重复的时间和空间将会是浪费的,所带来的感觉也只能是令人沮丧的。
2.链接期实例化器
链接期实例化器将在链接期产生程序需要的所有模板特化。当一个程序被链接时,这个实例化器将会由于缺少特化而不断重复创建目标代码,直到所有的特化都已经完全。生成缺少的特化需要不断地运行编译器,因此,链接期实例化器将在链接期编译代码(这是和编译期实例化器的区别)。编译后,生成的目标代码被放在一个用来存储模板特化的位置,通常称为储存库(repository)。
这种实现的算法从概念上看来是非常简单的,然而,如果要在程序首次编译的时候,程序能高效正确地运行,并且在程序由于改变而重新编译时,程序也可以高效地运行,那么这个实现就很困难了。幸运的是,编写一个正确而且高效的模板实例化器是编译器开发商所关注的问题,并不是可重用代码设计者和实现者关心的对象。
为了简化实例化,某些链接期实例化器要求模板源代码的组织形式符合某种约定。假设Hashtable的定义位于头文件Hashtable.h中:
- Hashtable.h:
- template<class T> ‘
- class Hashtable{
- //...
- };
那么某些实例化器会要求Hashtable中所有的非内联成员函数和静态成员变量的定义都应该放在一个名为Hashtable.c的文件中,这个文件和Hashtable.h位于同一个目录下:- Hashtable.c:
- template<class T>
- void Hashtable<T>::insert(const T& t){
- //...
- }
- //...
链接期实例化器所要求的源代码组织形式通常都是比较易于实现的,但这却会带来移植性问题。因为不同的实例化器将会施加不同的要求,而且有时候这些要求又是互相冲突的。因此,编写对于具有不同实例化器的平台仍然是可移植的代码,就需要在代码的创建过程中花费额外的精力。