具现
具现(instantiations):表示程序将真正的类型和表达式绑定到模板相关形式参数上的操作。也叫做实例化
举个例子:
template<class Type>
Type min(const Type& t1, const Type &t2){}
调用:
min(1.0, 2.0);
然后程序就会将Type绑定为double并产生min()的一个程序实体(并适当的mangling),其中t1、t2的类型都是double
模板如何"具现"
对于类模板:
template<class Type>
class Point{
public:
enum Status {unallocated, normalized};
Point(Type x = 0.0, Type y = 0.0, Type z = 0.0);
~Point();
void* operator new(size_t);
void operator delete(void *, size_t);
private:
static Point<Type> *freeList;
static int chunkSize;
Type _x, _y, _z;
};
首先,当编译器看到类模板的声明时,它会做出什么反应?
- 实际程序中,什么反应也没有
- 也就是说,上面的static data members并不可用。nested enum或者其enumerators也一样
虽然enum Status、enumerators的真正类型在所有的Point具现中都一样,但它们每一个都只能通过Point类模板的某个实体来存取或者操作。因此,我们可以这样写:
Point<float>::Status s;
但不能这样写:
// error
Point::Status s;
即使两种类型抽象的来说是一样的(而且,最理想情况下,我们希望这个enum只有一个实体被产生出来,如果不是这样,我们可能会会想要把这个enum抽出到一个nontemplete base class中,以避免多次拷贝)
同样,不能这样写:
Point::freeList;
Point::chunkSize;
必须明确指明其类型,才能使用freeList和chunkSize
Point<float>::freeList;
Point<float>::chunkSize;
像上面这样使用static member,会使其一份实体于Point类的float具现在程序中产生关联。如果这样写:
// 另一个实体(instance)
Point<double>::freeList;
就会出现第二个freeList实体,与Point类的double具现产生关联。
如果我们定义一个指针,指向特定的实体,比如:
Point<float> *ptr = 0;
这一次,程序中什么也没有发生。为什么呢?因为一个指向类对象的指针,本身并不是一个类对象,编译器不需要直到与该类有关的任何成员的数据或者对象布局数据。
如果定义的不是指针而是引用,又将如何?
const Point<float> & ref = 0;
它会具现出一个Point的float实体。这个定义的真正语意会被扩展为:
//内部扩展
Point<float> temporary(float (0));
const Point<float> & ref = temporary;
为什么呢?因为引用并不是no object的代名词。0被视为整数,必须被转换为如下类型的一个对象:
Point<float>
如果没有转换成功,那么这个定义就是错误的,编译通不过。
如果一个类对象的定义,不管是编译器隐式生成(上面的temporary)还是程序员像下面一样的显式定义:
const Point<float> origin;
都会导致类模板的具现,也就是说,float instantiation的真正对象布局被产生出来。回顾之前的模板声明:Point有三个nonstatic members,每一个类型都是Type。Type现在被绑定为float,所以origin的配置空间必须足够容纳三个float成员。
然后,对于那些未被使用过的memeber function不应该被“实体”化,只有在成员函数被使用的时候,C++保证才要求它们被具现出来。
比如,当:
Point<float> *p = new Point<float>
时,只有Point模板的float实例、new运算符、默认构造函数需要被具现。注意,虽然new运算符是这个类的一个implicitly static member,以至于它不能直接处理其中任何一个nonstatic members,但它还是一来真正的模板参数类型,因为它的第一个参值size_t表示类的大小。
这些函数在什么时候被"具现”出来呢?当前流行两种策略:
- 在编译时候。那么函数将"具现"于origin和p存在的那个文件中
- 在链接时候。那么函数将被一些辅助工具重新激活。函数模板实体可能被放在这个文件中、别的文件中,或者一个分离的存储位置上