標籤:

C++對象模型(2)構造函數語義學

構造函數是對象模型的首要部分,實際運用的構造函數的顯示部分實際所能提供的信息並沒有多少。編譯器為我們做的工作要遠遠多於我們的想像,甚至說,有一些部分於我們理所當然的認為相差甚遠。那麼,構造函數到底為我們做了一些什麼呢?

構造函數:

默認構造函數的存在是很多C++新手的誤區之一。許多人認為默認構造函數會為成員賦予一個初始的值,實際則不然。為成員變數賦初值只是程序員的需要,並非編譯器的需要,因此編譯器並不會負責程序員理想中的需要,只有當編譯器自身需要一個默認構造函數,或者程序員顯示指定一個默認構造函數時,默認構造函數才會真的存在。

程序員顯示指定比較容易理解,那麼什麼時候編譯器需要一個默認構造函數的呢?有以下四點。

1.成員對象有一個默認構造函數

2.父類有一個默認構造函數

3.類本身有支持虛函數機制

4.支持虛繼承父類

簡單來說,成員對象有一個默認構造函數時,為了構造成員對象,類本身的對象必須支持成員對象的構造函數,因此編譯器會創建一個默認構造函數。同理,父類有一個默認構造函數也是一樣,需要支持父類的構造函數。虛函數機制有些特殊,當類支持虛函數機制時,會產生一個虛表,在虛表中儲存的是類的虛函數的地址,這個虛表的地址無法被程序員顯示指定,由編譯器維護,因此默認構造函數必須要指定虛表地址(如果有虛表的話)。虛繼承涉及到了比較複雜的機制,他會在構造函數參數列表中生成一個bool類型變數,只有在最高級別子類(most derived class 不知道這麼翻譯恰不恰當...)中,才會通過bool類型量實例化一次虛基類中的對象,以此實現虛基類機制。

拷貝構造:

拷貝構造與默認構造類似,也是同樣在四個條件時會產生真正的拷貝構造,否則的話就是按位拷貝(bitwise copy)。

1.成員對象有一個拷貝構造函數

2.父類有一個拷貝構造函數

3.類本身有支持虛函數機制

4.支持虛繼承父類

1和2與默認構造類似,不再贅述,而虛函數機制的需求與構造函數稍有不同,在這裡主要防範的還是對象拷貝之後的虛表指針指向問題,編譯器為了確保虛表指針不會出現問題,會顯示指定虛表指針的地址,這樣就保證了不會出現炸裂(blow up)的問題。虛繼承機制類似於默認構造函數,為了防止出現虛基類的構造多對象,編譯器會介入拷貝過程。

默認拷貝構造函數有著來自編譯器的按位拷貝(bitwise copy)保證,只要對象中存在一項,那麼就一定會被最有效率的原樣拷貝,但是一旦自己定義了一個拷貝構造函數,那麼編譯器將不再作出如上保證,因此拷貝構造要視情況主動定義。

NRV(named return value)優化:

有如下定義:

X bar(){

X xx;

//處理xx

return xx;}

編譯器把其中的xx以_result取代:

void bar(X & _result){

//默認構造函數;

_result.X::X();

//處理_result;

return;}

這樣的編譯器優化被稱為NRV優化,它可以減少程序變數的幾個拷貝構造函數,默認構造函數的調用次數,通過這種方法實現編譯器優化。部分編譯期可以通過顯示的拷貝構造函數的聲明來顯示使用NRV優化,部分編譯器會默認調用這個優化。但是它也有幾個缺點,首先優化是否真的完成並不確定,其次一旦函數變得複雜,優化也就會較難執行。

成員初值列:

在以下四種情況時,必須使用成員初值列。

1.初始化一個引用成員

2.初始化一個const成員

3.調用父類構造函數

4.調用子類構造函數

但是成員初值列會遵守變數聲明的順序進行初始化,與寫在初值列的成員順序無關,在初值列中還可以調用類對象本身的成員函數,因為此時this指針已經安插到了參數列表中。但是不可以調用子類成員,因為此時子類成員尚未構造。來自編譯器的保證,成員初值列一定會在構造函數體運行之前運行。

附:

C++新手誤解:

1.class沒有默認構造函數,會被合成一個(實則不然)

2.class合成的默認構造函數會賦初值(實則不然)

構造函數實現步驟:

1.虛基類構建(在最高級別子類中,通過添加bool變數只構建一次)

2.父類構建

3.成員對象構建

4.虛表指針構建

5.成員初始化列表移動到構造函數體的頭部

6.運行構造函數體
推薦閱讀:

C++對象模型(1) 關於對象

TAG:遊戲編程 |