第5章 構造 解構 拷貝語義學

2021-08-22 14:49:32 字數 4492 閱讀 8029

我們可以定義和呼叫乙個純虛函式,不過只能被靜態呼叫,不能由虛擬機制呼叫:

inline void abstract_base::inte***ce() const

{}inline void concrete_derived::inte***ce() const

ps:類中的pure virtual destructor必須要定義,原因是每乙個派生類的析構函式會被編譯器加以擴充套件,以靜態呼叫方式呼叫其」每乙個虛基類「以及上一層基類的析構函式。如果缺少任何乙個基類的析構函式,會鏈結失敗。建議還是不要把虛析構函式宣告為pure。

通常不建議把所有的成員函式都宣告為虛函式,就算編譯器會進行優化而把非必要的虛函式宣告去除。

建議不在虛函式中使用const,否則帶來的麻煩有基類宣告為const,可能派生類中需要修改某些成員變數的值等。

typedef struct

point;

這種宣告被稱作plain ol『 data宣告形式,編譯器在遇到這種宣告時會為其貼上這個標籤。

class point

private:

float _x,_y,_z;

}void mumble()

; point local2;

//inline expansion

local2._x = 1.0;

local2._y = 1.0;

local2._z = 1.0;

}

local1操作會快於local2,因為explicit initialization list更快。

但是explicit initialization list也有一定的缺點:

1.只有當class members都是public時,此法才奏效

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

3.初始化行為失敗的可能性會高一些

class point

virtual float z();

private:

float _x, _y;

}

當引入vptr後class point物件的新產生的內容:

1.建構函式增加了一些內容,用來初始化vptr。這些內容附加在基類構造函式呼叫之後,explicit user code之前。具體的轉化過程如下:

point* point::point(point *this,float x, float y)

:_x(x),_y(y)

2.合成乙個複製建構函式,賦值操作符函式。

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

object t;
當我們定義了上面的變數時,當呼叫建構函式時,編譯器內部會進行大量的擴充操作,主要有:

1.記錄在成員初始化列表中的成員變數初始化操作會放進建構函式本身

2.如果有乙個成員沒有出現在初始化列表中,但它有自己的建構函式,那麼它的建構函式也要被呼叫。

3.在那之前,如果類物件存在虛函式機制,那麼vptr必須被設定初值

4.在那之前,所有上一層的基類建構函式要被呼叫,以基類的宣告順序為順序。

4.1 如果基類被列於初始化列表中,那麼任何明確指定的引數都應傳遞進去

4.2 如果基類沒有出現在初始化列表中,但它自身有建構函式,那麼要被呼叫

4.3 如果基類是多重繼承下的第二或後繼的基類,那麼this指標需要調整

5.在那之前,所有的虛基類建構函式要被呼叫,從左到右,從深到淺

5.1 如果該類被列於初始化列表中,那麼任何明確指定的引數都應傳遞進去,如果沒有,但它自身有建構函式,那麼要被呼叫

5.2 類中的每乙個virtual base class subobject的偏移量必須在執行期可被訪問

5.3 如果類物件是最底層的class,其建構函式可能被呼叫,某些用以支援這個行為的機制必須被放進來

下面我們舉個例子來說明下繼承體系下的類物件構造過程:

class point

protected:

float _x,_y;

}class line

line的建構函式會被擴充來呼叫兩個member class object的建構函式,在編譯器內部會轉化為:

line* line::line(line *this,const point& begin, const point& end)

析構函式同理,只是與構造的順序相反。

在這個例子中,我們分別砍下point3d和vertex3d在虛基類繼承情況下的構造函式呼叫情況:

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

vertex3d* vertex3d::vertex3d(vertex3d *this, bool __most_derived, float x, float y, float z)

vptr的初始化時機:在基類構造函式呼叫之後,在程式設計師**的**和初始化成員列表中成員初始化操作之前。例如:

pvertex::pvertex(float x,float y,float z)

:_next(0),vertex3d(x,y,z),point(x,y)

//編譯器內部擴充套件

pvertex* pvertex::pvertex(pvertex* this,bool __most_derived,float x,float y,float z)

ps:在class的建構函式內的初始化成員列表中呼叫該class的乙個虛函式,這樣是安全的嗎?

理論上來說這是安全的,因為vptr總是在初始化成員列表操作之前就已完成,但是在語義上可能是不安全的,因為函式本身可能還依賴未被設立初值的成員。

如果複製建構函式、賦值操作函式表現為bitwise copy,那麼編譯器就不會生成乙個預設的函式實體。

在下面幾種情況下,不會表現出bitwise copy語義:

1.當類中帶有乙個member class object,而該class有它自己的賦值操作符

2.當class的基類有賦值操作符

3.class繼承自乙個虛基類(不論它有沒有賦值操作符)

4.class有宣告虛函式

ps:賦值操作符缺乏乙個member assignment list,因此不能夠這樣寫:

inline point3d& 

point3d::operator=(const point3d &p3d)

:point(p3d),z(p3d._z){}

如果class內含的member class object(或其base class)含有析構函式時,編譯器才會合成出乙個析構函式,否則其他情況下都沒有必要合成它。

那麼為什麼當乙個class中的基類或類成員含有析構函式時要合成呢?原因是我們希望基類的析構函式被呼叫來完成必要的操作,如果想被呼叫,那麼該class的析構函式裡就要呼叫基類的,因此它的析構函式也是必要的。

在編譯器中析構函式的擴充套件過程:

1.析構函式本身現在執行,那麼vptr會在程式設計師所寫**之前被重設

2.如果class有member class object且後者有析構函式,那麼內部的class會以宣告順序相反的順序呼叫析構函式

3.如果object內帶乙個vptr,那麼首先重設相關的虛函式表

4.如果有任何直接的non-virtual base class有析構函式,那麼會以宣告順序相反的順序呼叫析構函式

5.如果有任何的虛基類有析構函式,且當前討論的這個class是最尾端的class,那麼會以宣告順序相反的順序呼叫析構函式

第3章 Data語義學

類的static data members被放置在程式的global data segment,nonstatic members資料直接存放在類物件中。類物件的實際大小,要考慮 1.由編譯器自動加上的額外data members,用來支援某些語言特性 2.位元組對齊的需要。nonstatic da...

建構函式語義學

有些書上說,如果乙個類中沒有任何的建構函式,那麼編譯器會為我們預設的合成乙個 合成預設規則函式 其實,系統是在 必要的時候 才會為我們合成預設的建構函式。這個可以去分析obj檔案 情況1 如果乙個類中沒有任何的建構函式,且它的成員變數中含有乙個類型別的成員,那麼這個時候系統會為這個類合成乙個預設的建...

建構函式語義學之程式轉化語義學 2

在 建構函式語義學之程式轉化語義學 1 中編譯器做了一些優化,有時他還會給你的程式更多的優化 1 在使用者層面做優化 如果程式設計師頂乙個計算用的 constructor x bar const t y,const t z x xx 以 y 和 z 來處理 xx return xx 有的編譯器開發人...