5.5 析构语义学

如果class没有定义destructor,那么只有在class内含member object(或者base含有)的destructor的情况下,编译器才会自动合成一个出来。
否则destructor 被视为不需要,也就不会被合成。例如,默认情况下,Point 没有被编译器合成出一个destructor--虽然它拥有一个virtual fucntion:

class Point
{
public:
	Point(float x = 0.0, float y = 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,更不要因为不确定是否需要一个destructor,于是就提供它.

为了决定 class 是否需要一个程序层面的destructor(或是constructor),请想想一个 class object的生命在哪里结束(或开始)?需要什么操作才能保证对象的完整?这是写程序时比较需要了解的.这也是constructor和destructor什么时候起作用的关键.
例子:

{
    Point pt;
    Point *p = new Point3d;
    foo(&pt, p);
    delete p;
}

pt 和 p 在作为foo()函数的参数之前,都必须先初始化。这时候需要一个constructor,否则使用者必须明确地提供坐标值。 一般而言,class 的使用者没法检验一个local变量或heap变量是否被初始化. constructor的工作有其必要性.如果没有它们,抽象化的使用就会有错误的倾向.
当明确地 delete 掉p时会如何呢? 在任何程序上必须处理的吗? 是否需要在 delete 之前这么做:

p->x(0);
p->y(0);

不需要。没必要在 delete 一个对象之前先得将其内容清除干净.也不需要归还任何资源.在结束pt和p的生命之前,没有任何"class使用者层面"的程序操作是必要的,因此,也就不一定需要一个destructor.
然而考虑Vertex class,它维护了一个由紧邻的"顶点"所形成的链表,并且当一个顶点的生命结束时,在链表上来回移动以完成删除操作.如果这正是程序员所需要的,那这就是Vertex destructor的工作。
Vertex3d 派生自 Point3d 和 Vertex:
Ⅰ、如果不提供一个 explicit Vertex3d destructor,那么还是希望Vertex destructor被调用,以协助结束一个Vertex3d object。因此,编译器必须合成一个Vertex3d destructor,其唯一的任务就是调用Vertex destructor。
Ⅱ、如果提供一个Vertex3d destructor,编译器会扩展它,使它调用Vertex destructor。程序员定义的 destructor 被扩展的方式类似constructors被扩展的方式,但顺序相反: 2,3,1,4,5
1.如果object内带一个vptr,那么首先重设(reset)相关的 virtual table。
2.destructor的函数本身现在被执行,也就是说 vptr 会在程序员的码执行前被重设(reset).
3.如果 class 拥有 member class objects,而后者拥有destructors,那么它们会以声明顺序的相反顺序被调用。
4.如果有任何直接的(上一层)nonvirtual base classes拥有destructor,它们会以其声明顺序的相反顺序被调用。
5.如果有任何的 virtual base classes拥有destructor,而当前讨论的这个 class 是最尾端(most-derived)的 class,那么它们会以其原来的构造顺序的相反顺序被调用。

就像constructor一样, 目前对于destructor的一种最佳实现策略就是维护两份destructor实体:
1.一个complete object实体,总是设定好vptr,并调用 virtual base class destructors.
2.一个base class subobject实体:除非在destructor函数中调用一个 virtual function,否则它绝不会调用 virtual base class destructors并设定vptr。
一个 object的生命结束于其destructor开始执行之时, 由于每一个base class destructor都轮番被调用,所以derived class 实际上变成了一个完整的object。例如一个 PVertex对象归还其内存空间之前,会依次变成一个Vertex3d对象,一个Vertex对象,一个Point3d对象,最后成为一个Point对象.当在destructor中调用member functions时,对象的蜕变会因为vptr的重新设定(在每一个destructor中,在程序员所提供的代码之前)而受到影响.在程序中实施destructor的真正语言将在第6章详述。

posted @ 2021-04-24 11:54  点|滴  阅读(6)  评论(0)    收藏  举报