5.5 析構語意學(Semantics of Destruction)
如果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中,在程式設計師所提供的程式碼執行之前)而受到影響。