C++物件模型之五 構造 析構 賦值筆記 (轉)

amyz發表於2007-11-13
C++物件模型之五 構造 析構 賦值筆記 (轉)[@more@] 

C++模型之五 構造 析構 賦值筆記:namespace prefix = o ns = "urn:schemas--com::office" />

1 無繼承下的構造

 用C++來編譯

 typedef struct

{

float x,y,z;

} point

會宣告一個:無效的預設構造,無效的析構,無效的複製,無效的賦值操作。然而並不實現它們。

當寫成類時體積並沒有改變

class point

{

 public:

  point (flaot x=0.0,float y = 0.0, float z = 0.0):_x(x),_y(y),_z(z){}

 //沒有構造,析構,複製

 private:

  float _x, _y, _z;

};

明顯初始化列表帶來的三項缺點

1.  只有當類成員都是public時,才有效。

2.  只能指定常量,因為它們在編譯時期可以被評估求值

3.  由於編譯器並沒有自動化施行之,所以初始化行為的失敗可能性會比較高

 

2 為繼承做準備

 class point

{

 public:

  point (flaot x=0.0,float y = 0.0):_x(x),_y(y){}

 //沒有構造,析構,複製

  virtual float z();

 private:

  float _x, _y;

};

體積因有了vtbl所以多vptr 4個位元組的空間。

1 構造被附加了些程式碼以便初始化vptr.被加在基類建構函式之後,員寫的程式碼之前,可能是

point * point ::point( point *this,float x,flaot y):_x(x),_y(y)

{

 this->_vptr_point = _vtbl_point;  //初始化vptr

  this->_x = x;  //擴充套件成員初始化列表

this->_y = y;

return this;

}

2 合成一個複製函式和一個複製賦值操作

inline point * point::point ( point *this, const point &rhs)

{

this->_vptr_point = _vtbl_point ;

return this;

}

賦值操作採用位複製技術。

繼承體系下的構造

一般而言編譯器所做的擴充操作大約如下

1.  記錄在成員初始化列表中的資料成員,初始化操作會被放進建構函式本身,以宣告為序。

2.  如果有個成員沒有出現在初始化列表之中,但它有個預設建構函式,將被呼叫。

3.  如果類物件中有vptr它將被設定初始值,指向vbtl。

4.  所有上一層基類構造必須被呼叫,以宣告為序。

1.  如果基類被放在初始化列表中,那麼任何明確指定的引數都應該傳遞過去。

2.  如果基類沒有被放在初始化列表中,而它有個預設建構函式將被呼叫。

3.  如果基類是多重繼承下的第二或後繼的基類,那麼this指標必須調整。

5.  所有虛基類建構函式必須呼叫,從左到右,從最深到最淺:

1.  如果類被放在初始化列表中,那麼引數將傳過去,沒有放在,而類有構造將呼叫。

2.  此外,類中的每個虛基類子物件的偏移量必須在期可被存取。

3.  如果基類是最底層的類,其構造可能被呼叫,某些機制將會放進來。

class point

{

 public:

point (flaot x=0.0,float y = 0.0)

point (const point &);

point & operator = (const point &);

virtual ~point();

virtual float z(){return 0.0;}

 private:

  float _x, _y;

};

3虛繼承下

class point3d:public virtual point

{

 public:

point3d ( float x = 0.0, float y=0.0, float z =0.0):point (x,y),_z(z){}

point3d( const poin3d& rhs):point(rhs),_z(rhs.z){}

~point3d();

point3d & operator = (const point3d &);

virtual float z() {return _z;}

protected:

float _z;

};

下面就是point3d的建構函式被擴充的內容

point3d *point3d::point3d( piont3d *this ,bool _most_derived,float x, float y, float z)

{

if ( _most_derived != false) //在C++中虛基類的建構函式由最底層的類負責呼叫,所以要判斷自己有沒有派this->point::point(x,y);  //所以要判斷自己有沒有派生類

this->_vptr_point3d = _vtbl_point3d;  // 初始化自己的vptr

this->vptr_point3d_point = _vtbl_point3d_point; //初始化基類的vptr

this->_z = rhs.z;

return this;

}

4vptr初始化

class Vertex:virtual public point{….};

class Vertex3d:public point3d,public Vertex {…..};

class Pvertex:public Vertex3d{….};

它們中都有個virtual size()

構造呼叫順序是:由根源而末端,由內而外。在建構函式中呼叫虛擬函式被評估為本類的函式而不是派生類的,多型特性在物件構造中無效。

1.  在派生類中所有的虛基類及上一層基類的建構函式被呼叫。

2.  上述完成後物件的vptr被初始化,指向vbtl

3.  如果有成員初始化列表的話,將在構造內展開,必須在vptr被設定後進行

4.  最後執行程式設計師提供的程式碼。

Pvertex ::Pvertex ( float x, float y, float z):_next(0),Vertex3d(x,y,z),point(x,y)

{

 if (spyOn)

 cerr<

}

被擴充為:

Pvertex *Pvertex::Pvertex( Pvertex *this ,bool _most_derived, float x, float,y,float z)

{

 if ( _most_derived != false)

 this->point::point(x,y);

this->vertex3d::vertex3d(x,y,z);

this->_vptr_Pvertex = _vtbl_Pvertex;

this->_vptr_point_Pvertex = _vbtl_point_Pvertex;

if ( spyOn)

 cerr<_vptr_Pvertex[3].faddr)(this)<

return this;

}

物件賦值

賦值操作將在以下情況下需要

1.  當類內帶有一個物件成員,而其類有個賦值操作。

2.  當本類的基類有個賦值操作時。

3.  當本類宣告瞭任何虛擬函式。

4.  當本類繼承自一個虛繼承類時,無論基類是否有賦值操作。

Inline point & point::operator = ( const point &p)

{

 _x = p._x;

 _y=p._y;

return this;

}

class point3d :: virtual public point

{…..}

編譯器將為point3d合成一個賦值操作

inline point3d & point3d::operator = (point3d *const this,const point3d &p)

{

 this->point::operator=(p);

 _z=p._z;

return *this;

}

因為C++標準沒有賦值列表,所以造成派生類賦值操作中將會重複呼叫基類的賦值操作

inline vertex3d&  vertex3d::operator=(const vertex3d &v)

{

  this->point::operator=(v);

  this->point3d::operator=(v); //內含有this->point::operator=(v);

this->vertex::operator=(v); //內含有this->point::operator=(v);

return this;

}

建議儘可能不要允一個虛基類的賦值操作,不要在任何虛基類中宣告資料。

物件功能

測試物件的構造和賦值操作成本

struct point3d {float x,y,z;};

class point3d {public : float x,y,z;};

 

未最佳化

結構體

5.84

7.22

內聯構造逐位

6.00

7.33

內聯構造帶虛擬函式非逐位

7.67

13.05

單一繼承內聯逐位

6.26

7.33

單一繼承內聯非逐位

10.35

17.74

單一虛繼承內聯非逐位

17.29

23.93

多重繼承內聯逐位

6.26

7.33

多重繼承內聯非逐位

12.39

20.39

多重虛繼承內聯非逐位

19.31

26.80

 

析構

如果類沒有定義解構函式,那麼只有在類內帶的物件成員並且物件成員有析構或者基類擁有析構的情況下,編譯器才會自動合成出一個來,否則是不需要的。

class point

{

public:

 point (float x = 0.0 ,float y=0.0);

 point (const point &);

virtual float z();

….

Private :

Float _x,_y;

};

既是類point擁有虛擬函式,編譯器也不會合成一個解構函式。既是類lines資料成員是point beging,end; 也沒有必要因為point本身沒有解構函式。

你應該拒絕被稱為對稱策略的想法:“你已經定義了構造就必須定義個析構”。

決定為類是否在程式層面提供解構函式要看: 類物件生命在哪裡結束?需要什麼操作才能保證物件的完整性。這也是構造和解構函式什麼時候起關鍵作用。

Main()

{

 point pt;

 point *p=new point3d;

 foo( &pt,p);

 ….

Delete p;

}

pt和p在函式foo()前要初始化,否則使用者必須明確提供座標,一般而言類的使用者這沒有辦法檢驗一個本地變數或者堆變數是否已經初始化了,所以這時建構函式工作是有必要的。同樣在delete p之前對p是否要進行處理保證物件的完整性 比如:p->x=0;p->y=0;

目前對於析構的一種最佳策略就是維護兩份析構實體

1 一個完整物件實體,總是設定好vptrs,並呼叫虛基類解構函式。

2 一個基類子物件實體,除非在解構函式中呼叫一個虛擬函式,否則不會去呼叫虛基類的解構函式並設定vptr

一個物件的生命結束於其解構函式開始執行之前時,由於每個基類解構函式都輪番被呼叫,所以派生類實際上變成一個完整的物件。如Pvertex物件歸還空間時,會依次變為一個Vertex3d,Vertex,Point3d,Point.

析構順序:

1 解構函式本身首先執行

2 如果類有成員物件,而後者擁有解構函式,那麼它們會以宣告的順序相反的順序呼叫。

3 如果物件帶有個vptr,則現在被重新設定,指向適當的基類vtbl.

4 如果有任何直接上一層非虛基類擁有解構函式,那麼它們會以宣告的順序相反的順序呼叫。

5 如果有任何虛基類擁有解構函式,並且時最尾端的類,那麼它們會以原來的順序相反的順序呼叫。

 

  曾牧暗鯊,網名:大白鯊 -8-16

 

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-982171/,如需轉載,請註明出處,否則將追究法律責任。

相關文章