Template实例化instantiation是由泛化的template定义式产生出实际类型和函数的过程。C++template实例化是一个基础而复杂的概念。说它复杂的一个原因是,由template产生的物体entities的定义不限于源码的某一单独位置上。template所在位置、template被使用位置、以及template argument定义位置,都在构成物体意义一事上扮演各自的角色。
本文·讲述如何组织我们的程序代码才能以正确方式运用template。我们还将讲述为解决实际实例化问题,大多数流行的C++编译器所使用的方法,尽管这些方法应该是语义等价的。
1. 随需实例化on-demand instantiation
当C++编译器见到程序中使用一个template特化体specialization,便将template parameters替换为所需的argument,以创建出这个特化体。这个过程是自动完成,无需借助任何client程序码或template定义的指示。正是这种随需实例化的特性使得C++template机制有别于其它语言的类似机制。这种机制有时也称为隐式implicit或自动automatic实例化。
随需实例化意味着,当程序用到template时,编译器需要得知该template和其某些成员的完整定义(而不仅仅是template的声明)。考虑下面的例子:
template<typename T> class C; // (1) 只有声明
C<int>* p=0; // (2) 没问题,不要 C<int> 的定义
template <typename T>
class C{
public:
void f(); // (3) 成员声明
}; // (4) class template定义完备
void g (C<int>& c) //(5) 只用到class template的声明
{
c.f(); //(6) 用到了class template的定义:需要C::f()的完整定义
}
在(1)处,编译器只能见到template的声明,见不到其定义(这种声明又称为前置声明,forward declaration)。和常规classes的规则一样,不需要class template的定义,我们就可以定义给类型的指针pointer或引用reference,如这里的(2)。例如函数g()的参数类型C<int>&并不需要template C的完整定义。然而一旦编译器需要知道某个template特化体的大小或程序代码中取用了该特化体的某个成员,编译器就必须见到template的定义。这说明为什么在(6)处,class template的定义必须可见:如果见不到这个定义,编译器就无法确认这个成员是否存在,或程序代码是否有权取用它(必须不为private或protected,才能被取用)。
这里有另外一个表达式,需要class template实例化,因为此处编译器必须知道C<void>的大小。
C<void>* p = new C<void>;
这里必须完成实例化,这么一来编译器才知道C<void>的大小。你可能会注意到无论把template C的参数T以什么样的类型X替换,其template实例化的大小都不会受到影响:任何情况下C<X>都是个empty class。编译器也需要以实例化过程来得知C<void>是否一个可被取用的default构造函数,并确保C<void>没有定义private operator new 和 private operator delete。
有时候,从程序代码本身并不能看出某个class template的成员会被取用。例如C++的重载解析机制overload resolution便需要得知各候选参数的函数额class types:
template <typename T>
class C {
public:
C(int);
};
void candidate(C<double> const&); //(1)
void candiate(int) {} //(2)
int main()
{
candidate(42); //上面声明的两个函数都可被调用
}
编译器会将candidate(42)解析resolve为(2)的声明。然而(1)处的声明也可能被实例化,用来确定它是否也是上述调用的一个合法候选函数(本例之中实例化可能发生),因为42可隐寓通过单自变量构造函数转型为C<double>类型的 rvalue。
2.缓式实例化Lazy instantiation
3.C++实例化模型Instantiation Model
4.实现方案Implementation Schemes
5.显式实例化Explicit Instantiation
6.小结