1. 程式人生 > >5.5 析構語意學(Semantics of Destruction)

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的相反順序):

  1. 如果object內含一個vptr,那麼首先重設相關的vtbl。
  2. destructor的函式體被執行,vptr會在程式設計師的程式碼之前被重設。
  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例項,總是設定好vptrs,並呼叫virtual base class destructor。
  2. 一個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中,在程式設計師所提供的程式碼執行之前)而受到影響。