目录
第10章 实例化
模板实例化是一个过程, 它根据泛型的模板定义, 生成(具体的) 类型或者函数。 在C++中, 模板实例化是一个很基础的概念, 但却多少有一些错缩复杂。 复杂性的一个主要原因在于: 对于产生自模板的实体(指具体类型或函数) , 它们的定义已经不再局限于源代码中的单一位置。 事实上, 模板本身的位置、 使用模板的位置、 定义模板实参的位置都会对这个(产生自模板的) 实体的含义产生一定的影响。
10.1 On-Demand实例化
当C++编译器遇到模板特化的使用时, 它会利用所给的实参替换对应的模板参数, 从而产生该模板的特化。 这个过程是编译器自动进行的, 并不需要客户端代码来引导(或者不需要模板定义来引导) 。 而且, on-demand实例化的这个特性也使得C++模板和其他编译型语言的相似功能大有区别。 另外, on-demand实例化有时也被称为隐式实例化或者自动实例化。
on-demand实例化表明: 在使用模板(特化) 的地方, 编译器通常需要访问模板和某些模板成员的整个定义(也就是说, 只有声明是不够的) 。 考虑下面这个包含短小源代码的文件:
template<typename T>
class C;//(1)这里只有声明
C<int>* p = 0; //(2)正确:并不需要C<int>的定义
template<typename T>
class C
{
public:
void f(); //(3) 成员声明
}; //(4) 类模板定义结束
void g (C<int>& c) //(5) 只使用类模板声明
{
c.f(); //(6) 使用了类模板的定义
} // 需要C::f()的定义
在源代码的(1) 处, 只有模板声明是可见的, 也就是说: 模板定义此时还不是可见的(这类声明有时也被称为前置声明) 。 与普通类的情况一样, 如果你声明的是一个指向某种类型的指针或者引用(如(2) 处的声明) , 那么在声明的作用域中, 你并不需要看到该类模板的定义。例如, 声明函数g的参数类型并不需要模板C的完整定义。 然而, 如果(某个组件) 期望知道模板特化的大小, 或者访问该特化的成员, 那么整个类模板的定义就需要位于作用域中; 这也是源代码的(6) 处需要模板定义的原因。 因为如果看不见这个模板定义的话, 编译器就不能确定成员f存在且是可访问的(就是说, 不是私有的, 也不
是受保护的) 。
下面是另一个需要进行(前面的) 类模板实例化的表达式, 因为编译器需要知道C<void>的大小:
C<void>* p = new C<void>;
在这个例子中, 实例化是必不可少的, 因为只有进行实例化之后,编译器才能知道C<void>的大小。 对于上面这个特殊的模板, 你可能会认为: 用任何类型的实参 X 替换参数T之后, 都不会影响模板(特化)的大小; 因为在任何情况下, C<X>都是一个空类。 然而, 编译器并不会检测它是否为空。 而且, 为了确定 C<void>是否具有可访问的缺省构造函数, 并且确认C<void>没有声明私有的operator new 或者operator delete, 我们需要进行实例化。
10.2 延迟实例化
现在就有了一个相关的问题: 模板的实例化程度是怎么样的呢? 对于这个问题, 一个模糊的回答会是: 只对确实需要的部分进行实例化。换句话说, 编译器会延迟模板的实例化。 让我们细究“延迟”在这里的具体含义。
当隐式实例化类模板时, 同时也实例化了该模板的每个成员声明,但并没有实例化相应的定义。 然而, 存在一些例外的情况: 首先, 如果类模板包含了一个匿名的 union, 那么该union 定义的成员同时也被实例化了。 另一种例外情况发生在虚函数身上: 作为实例化类模板的结果, 虚函数的定义可能被实例化了