继续整理第五章剩下的内容,关于析构函数语义的。
1. 如果类没有定义析构函数,那么只有类中含有成员对象(或者本类的基类)拥有析构函数的情况下,编译器才会合成一个出来,否则析构函数被视为不要,也就不需要合成。例如,如下类,虽然Point类拥有虚函数:
class Point {
piblic:
Point(float x = 0.0, float y = 0.0);
Point (const Point&);
virtual float z();
private:
flaot _x, _y;
};
同样的道理,如下类也不需要合成析构函数:
class Line {
public:
Line(const Point&, const Point&);
virtual draw();
protected:
Point _begin, _end;
};
当由Point类派生出(包括虚继承)Point3d类时,如果没有声明虚函数,那么编译器就不会合成一个。
之所以有上述机制,当此语句发生时:
Point *p = new Point3d;
必须调用构造函数初始化类对象,没有构造函数,抽象化的使用就会有错误的倾向。当我们delete掉一个类对象时:
delete p;
不需要在delete之前做类似的操作:
p->x(0);//x(float)和y(float)是Point类的成员函数
p->y(0);
结束p声明之前,没有任何类使用者层面的程序操作是程序员所必需的,因此,也就不一定需要一个析构函数。
2. 对于多重继承和虚拟继承而言,情况要有所不同,当我们从Point3d和Vertex(虚拟继承自Point)派生出Vertex3d时,如果我们不声明Vertex3d的析构函数,但我们仍然希望Vertex3d对象结束时,调用Vertex的析构函数,那么编译器此时必须合成一个Vertex3d的析构函数,该函数唯一的任务就是调用Vertex的析构函数,如果我们提供一个Vertex3d的析构函数,编译器会扩展之,在我们的代码之后调用Vertex的析构函数,该扩展与构造函数的扩展类似,但是顺序相反:
a. 析构函数本身被执行。
b. 调用成员类对象的析构函数(如果有的话),调用顺序使他们声明的相反顺序。
c. 重置虚表指针。
d. 调用非虚基类的构造函数(如果有的话),调用顺序使他们声明的相反顺序。
e. 调用虚基类的构造函数(如果有的话),如果当前类是最尾端(most-derived)的类,那么虚基类用原来构造顺序的相反顺序析构。
一个类对象生命结束于其析构函数调用开始执行之时,由于每一个基类析构函数都会被调用,所以子类实际上变成了一个完整的对象,例如:对于类d继承自c,c继承自b,b继承自a,当d对象析构时,会依次变成一个c对象,b对象,a对象,当我们在析构函数中调用成员函数时,对象的蜕变会因为虚表指针的重新设定(析构函数中,程序员所供应的代码执行之前)而受到影响,这个会在第六章的内容中整理。
第五章的内容整理到此,之后会继续第六章内容的整理。