现今某些C++(www.cppentry.com)开发环境能够辨识的人工实例化结构和上面描述的是不同的。另一种广泛使用的结构使用#pragma,这个用于指定实例化内容的语法可以让程序员表达实例化的内容,例如"在此产生Hashtable<Widget>中名称以'put'开头的所有非内联函数的定义"。当然,依赖于这些机制的代码是不可移植的。
某些C++(www.cppentry.com)实现提供了自动和人工两种实例化机制。明智地使用人工实例化可以减少由于单一使用自动实例化而导致的创建(build)时间。程序员通常都可以判断出最佳的实例化内容和位置,而自动的实例化器却不能。如我们在9.7.1节讨论的那样,对于大型的复杂程序,自动化实例化器往往会浪费大量的空间和时间;而人工实例化可以减少这些资源消耗,美中不足的是,这些节省是以程序员必须花费时间决定实例化的内容和位置为代价的。
由于传递的相关性,所以决定实例化的内容和位置是很困难的。假设我们的程序使用了一个Hashtable<Widget>,并且我们决定人工实例化我们程序中使用的所有特化:
所需要的特化,如:- template class List<Widget>;
如果List<Widget>使用了其他的模板,我们还需要实例化这些模板。
如果我们是Hashtable的实现者,那么实例化List<Widget>将不是费力的事情;然而,如果我们根本就不了解Hashtable的实现细节,那么我们可能就只能写出上面第一个实例化,当链接程序的时候,将会有错误告诉我们:和List<Widget>相关的某些函数和成员没有定义(即没有实例化):
- error:undefined function:
- List<Widget>::insert(const Widget&)
如果程序非常地大,我们就很难知道究竟何处需要List<Widget>::insert的定义;况且我们可能不愿意花费时间来找出这个位置。于是,我们会增加上面第二个人工实例化到我们的程序中。因此,我们就必须不断重复程序的链接过程,不断手动地增加实例化,直到所有的函数和成员都有定义为止。
当增加一个人工实例化的时候,还必须#include相应的头文件:
- #include<List.h>
- #include<Widget.h>
- //...
- template class List<Widget>;
然而,错误消息并没有告诉我们包含List声明的头文件名称。因此,为了使用户确定这个文件的名称,包含Hashtable和List的程序库要么使用统一的文件命名约定,要么在用户需要人工实例化某个模板时,对该模板所需要包含的头文件,程序库给出相应的文档。虽然List的存在只是程序库的实现细节,并且也并不允许外部公共使用,但在这里,我们不得不公布List模板定义的位置。
链接一个大的程序通常是一个很慢的过程。为了辨识所有需要的人工实例化,而不断重复地链接程序是一个更慢的过程。为了减小需要链接的次数,程序库的文档可以指定实例化的相关性:
实例化Hashtable<T>的程序必须同时实例化List<T>。
当程序员读到这个文档的这些内容的时候,将会一次人工实例化这两部分,从而节省了一次链接过程。
2.人工实例化和代码更改
如果程序就只编写一次,并且从不更改,那么人工实例化就是很实用的技术;然而,当我们更改程序时,人工实例化却会遇到重重困难。假设我们重新实现Hashtable,让它采用封闭链而非开放链(就是说,当冲突出现的时候,通过某种算法,我们在表中找到另一个位置来存储这个给定的值),那么我们需要如下修改Hashtable:
- template<class T>
- class Hashtable {
- private:
- T* table[1024];//上一版本这里是List<T> the_table[1024]
- //...
- };
所定表的每个入口(即指针)都指向存储在位置的值,如果这个值不存在,那么这个指针为0。因为我们不使用开放链,所以这里将不再需要使用List来实现Hashtable。
当完成了这个改变之后,使用Hashtable<Widget>的程序可能仍然需要(也可能不需要)实例化List<Widget>(因为在程序的别处也可能使用了List<Widget>)。如果程序不再需要List<Widget>,那么包含下面人工实例化指令
- template class List<widget>
将会导致产生不需要的代码,增加了可执行代码。
即使一个很小的改变,也可能会导致程序需要的实例化发生很大的改变。那么,在发生了改变之后,程序员应该如何决定所需要改变的最小实例化集合呢?如果使用的是人工实例化,那么一种肯定可以找到不需要的实例化集合的现实方法就是,删除所有的人工实例化,从头开始重复整个人工实例化过程。