如果class没有定义destructor,那么只有在class内含的member object(或class自己的base class)有拥有destructor时,编译器才会自动合成一个出来。否则,destructor被视为不需要,也就不需要合成(当然更不需要调用)。
例如,Point,默认情况下并没有被编译器合成一个destructor——甚至虽然它拥有一个virtual function:
class Point
{
public:
Point(float x = 0.0,float y = 0.0,float z = 0.0);
Point(const Point&);
virtual float z();
//...
private:
float _x,_y;
};
类似道理,如果我们把两个Point对象组合成一个Line class:
class Line
{
public:
Line(const Point&,const Point&);
//...
virtual draw();
//...
protected:
Point _begin,_end;
};
Line也不会拥有一个合成出来的destructor,因为Point并没有destructor。
当我们从Point派生出Point3d(即使是一种虚拟继承)时,如果我们没有声明一个destructor,编译器也就没不要合成一个destructor。
不论Point还是Point3d都不需要destructor,为它们提供一个destructor反而是低效率的。我们应该拒绝对称性的想法:定义了constructor,就必须定义destructor,更不要在不确定是否需要的时候提供destructor。
为了决定class是否需要一个程序层面的constructor(或destructor),就要知道一个class object的声明的开始(或结束),例如:
{
Point pt;
Point* p = new Point3d;
foo(&pt,p);
...
delete p;
}
pt和p在作为foo()函数的参数之前,都必须先初始化某些坐标,就需要一个constructor,否则使用者必须显示提供坐标。
当我们显示的delte掉p,是否在delete之前这么做:
p->x(0);p->y(0);
没有任何理由说delete一个对象之前得将其内容清理干净,你也不需要归还任何资源。因此不需要一个destructor。
请考虑Vertex class,它维护了一个紧邻的“顶点”所形成的链表,并且当一个顶点生命结束时,在链表上来回移动完成删除操作,这就是Vertex destructor工作。
当我们从Point3d和Vertex派生出Vertex3d时,如果我们不提供一个explicit Vertex3d destructor,因此编译器会合成一个Vertex3d destructor,其唯一的任务就是调用Vertex destructor。我们我们提供了一个Vertex3d destructor,编译器就会扩展它,使它调用Vertex destructor(在我们提供的程序代码之后)。一个由程序员定义的destructor被扩展的方式类似constructor被扩展的方式,但顺序相反(似乎应该是2、3、1、4、5才符合constructor的相反顺序):
- 如果object内含一个vptr,那么首先重设相关的vtbl。
- destructor的函数体被执行,vptr会在程序员的代码之前被重设。
- 如果class拥有member class objects,而后者拥有destructors,那么它们会以其声明顺序的相反顺序被调用。
- 如果任何直接的(上一层)nonvirtual base classes拥有destructor,它们会以其声明顺序的相反顺序被调用。
- 如果有任何的virtual base classes拥有destructor,目前这个class是最尾端(most-derived)的class,那么它们以其原来的构造的相反顺序被调用。
就像constructor一样,目前对于destructor的一种最佳的实现策略就是维护两份destructor实例:
- 一个complete object实例,总是设定好vptrs,并调用virtual base class destructor。
- 一个base class subobject实例;除非在destructor函数中调用virtual function,否则它绝对不会调用virtual base class destructor并设定vptr。
一个object声明结束期destructor开始执行之时。由于每个base class destructor被轮番调用,所以derived object实际上变成了一个完整的object。例如一个PVertex对象归还内存空间前,会依次变成一个Vertex3d对象、一个Vertex对象,一个Point3d对象,最后变成一个Point对象。当我们在destructor中调用member function时,对象对象的蜕变会因为vptr的重设(在每一个destructor中,在程序员所提供的代码执行之前)而受到影响。