在使用c++時,常常會好奇或者抱怨,編譯器為我們做了什麼事呢? 為什麼建構函式沒有為我初始化呢?為什麼我還要寫預設建構函式呢?
2.1 default constructor 的構造操作
如果沒有宣告預設建構函式,編譯器會在需要的時候幫我們產生出來。 為了避免在多個地方被需要導致重複,則編譯器將產生的建構函式宣告為inline方式。
class foo ;
class bar
bar bar; // 則會呼叫bar的預設建構函式,同時該預設建構函式會初始化member object成員,呼叫其建構函式
編譯器在bar中插入**:
inline bar::bar() ;但不會初始化str,需要程式設計師來進行初始化;
【重點】編譯器的行為是:如果class a內涵乙個或者乙個以上的member class objects,那麼class a 的每乙個constructor必須呼叫每乙個member classes的default constructor。 如果多個member class object是都需要初始化,則根據在class中宣告的順序進行初始化;
如果已經有明確宣告的建構函式,則不會在生命預設的建構函式,但仍需要在所有宣告的建構函式中呼叫包含或者繼承的member class objects相應的建構函式;如果沒有明確的建構函式,則需要合成default constructor,並呼叫相應的member class objects的constructor。
2)帶有virtual function的class
編譯器有兩個擴張行為:每乙個class生成乙個vtable,包含virtual function位址;其次每個class object中包含乙個pointer member(vptr)並指向vtable; 【注意】 每乙個包含virtual函式的class都有乙個vtable(自己帶有virtual或者從父類繼承過程的virtual,都需要建立乙個vtable,和父類的vtable獨立,這樣才能夠保證執行時多型,子類指標或者引用轉換為父類的引用或者指標,還是指向該類物件所指向的vtable(只是指標所指向記憶體布局不同)
以下4中情況會造成編譯器為未宣告的classes合成乙個default constructor。 (implicit nontrivial default constructors),被合成的constructors只用來滿足編譯器的需要。
1)member object(包含)或者base class(繼承)的default constructor(先初始化父類base constructor,其次是member object)
2)為每乙個object初始化其virtual function機制或virtual base class機制(多重機制僅保留乙份)
如 class x
class a:public virtual x class b:public virtual x
class c:public a,public b
void foo(const a *pa) (pa->i = 1024) 轉換為 pa -> _vbcx->i = 1024, 則_vbcx需要在virtual繼承過程的default constructor中初始化,保留僅有乙份;
誤解:
1)任何class如果沒有定義default constructor,就會被合成出乙個(只有被需要時才會)
2) 編譯器合成出來的default constructor會顯示設定"class 內每乙個data member的預設值「 (顯然不會)
2.2 copy constructor 的構造操作
出現的三種情景:
1) x x; x xx = x;
2) foo(x x) 函式引數
3) x foo() 函式返回值 => 轉換為 foo(x &result)
default memberwise initialization
一種data memer(乙個指標或者陣列)從某乙個object拷貝到另乙個object上(bitwise copy semantics 位逐次拷貝)
但不會拷貝member class object(而是以遞迴的方式呼叫該member class object的copy constructor,所謂的memberwise initialization)
四種情況不會bitwise copy semantics
1) 當class 中內含乙個member object ,而該object內含copy constructor(包含合成的)
2)class 繼承乙個base class
3)class 宣告了乙個或者多個virtual functions
4)class 派生自乙個繼承鏈只能夠,其中乙個或者多個virtual base classes時
主要介紹第三種情況,第四種情況太複雜,暫時不考慮
bear 包含virtual function, 繼承自 zooanimal(包含virtual function),都包含virtual void draw(){}
bear yogi;
bear winnie = yogi;
zooanimal franny = winie; // 如果實施bitwise copy semantics,則franny中的vptr則等於winie中的vptr;
則franny.draw() 將呼叫bear中的draw,顯然錯誤。 包含vptr的sliced強制拷貝,保留父類的vptr指標,則franny中的vptr仍指向zooanimal中的vtable。(補圖)
總結:object自己生成的data member不會被賦值(通過copy or assignment constructor),這樣可以保證自身獨立性
2.3 程式轉換語義學
分貝針對copy constructor存在的三種情況進行介紹,如何在編譯層面進行轉換,不同編譯器實現不同(採用何種優化方式)。主要介紹返回值時如何呼叫copy constructor,這樣方便程式設計師進行優化,主題提出的思路是新增函式第乙個引數來替代函式的返回值。函式的返回值編譯器自動優化為nrv。
t foo() ; =>編譯器優化void foo(t &result) =>程式設計師優化void foo(t &result)
2.4 成員們的初始化列表 member initialization list
適應情況:
1) 當初始化乙個reference member時 必須
2) 當初始化乙個const member 時,必須
3) 當呼叫乙個 base class 的constructor,而它擁有一組引數時,可以提高效率
4) 當呼叫乙個member class的constructor,額它擁有一組引數時,可提高效率
class word
則會轉換為,word建構函式中首先呼叫sting的建構函式初始化_name,然後呼叫string的拷貝建構函式對0初始化,並生成臨時變數string tmp,然後呼叫賦值建構函式operator=,將值賦給_name,最終呼叫destructor來銷毀_tmp。
而通過member initialization list,則將直接可以呼叫string(0)實現_name構建。
word():_name(0)
初始化列表順序和**位置
初始化列表的順序和data member宣告的順序相關,和列表中初始化順序無關,並且編譯器會生成**,將初始化列表中**插入到constructor中,並且一定在explicit user code之前。
int i;
int j;
word():j(val),i(j) {}
出現問題,先初始化i,而j未知,導致error。
深度探索C 物件模型筆記 第二章
explicit關鍵字能夠制止單參建構函式被當作型別轉換運算子.編譯器的隱式操作只是為了滿足編譯器本身的需求,而不是程式本身,乙個被編譯器隱式生成的預設建構函式,多數情況下對於程式本身來說是無用的.如果乙個類沒有任何建構函式,但類中的乙個非內建型別成員變數有預設建構函式,那麼這個類被隱式生成的預設建...
《深度探索C 物件模型》第二章 建構函式語意學
default constructor的構建操作 default constructors在需要的時候被編譯器產生。例 clas foo void foo bar 上述的 情況中,並不會生成乙個deafult constructor。需要注意的地方是 全域性的object內存在被啟用時會清0,而區域...
深度探索C 物件模型 第二章讀書筆記
1.如果設計者提供多個constructors,但其中都沒有default constructor呢?編譯器會擴張現在的每一次constructors,將 用以呼叫所有必要之default constructor 的程式 加進去。它不會合成乙個新的default constructor,因為其他由u...