C++裡,如果程式員沒有顯式的定義預設建構函式(default constructor),編譯器會在需要的時候產生一個,也就是隱式地聲明出來。
隱式聲明的預設建構函式有兩種,一種是trivial(無用的) constructor,什麼都不做;另一種是nontrival constructor,編譯器合成的是後者。
在四種情況下,編譯器需要合成nontrival constructor:
1. 帶有"Default Constructor"的成員類對象
class A {public: A(), ... };class B {public: A a;};int f(){ B b; //此處須調用B的預設建構函式}
因為B::a是一個member object,且其class A擁有default constructor,所以編譯器在需要調用B的預設建構函式時,會為B產生如下的預設建構函式
inline B::b(){ // C++偽碼 a.A::A(); // 調用a的預設建構函式}
但需要注意的是,編譯器產生的預設建構函式不會初始化基本類型的成員變數。
如果class B中有int類型的變數,其值不會被預設建構函式初始化為0。
還有一種情況,如果class B已經擁有一個預設建構函式,但其中並未對其對象成員進行初始化,如下例:
class B {public: A a, int num};B::B() {num = 0}
對於這種情況,編譯器會擴張已有的constructor,將對象成員的構造過程安插在user code前,像這樣:
B::B(){ a.A::A(); // 插入的compiler code num = 0; // user code}
另外還需注意的是,編譯器所安插的代碼會按照“對象在class中的聲明順序”來調用各個constructor,在user code前。
2. "帶有Default Constructor"的Base Class
如果一個class派生自一個“帶有default constructor”的base class,那個這個class的default constructor需要被編譯器合成出來,以調用上一層base class的default constructor。
很多人都瞭解“子類在調用建構函式時,會先調用父類的建構函式”,這正是因為編譯器在我們定義的constructor中進行了上述擴充。
3. "帶有Virtual Function"的Class
虛函數表(vtbl)的相關實現機制超出了本文的範圍,讀者可找相關文章或書藉來學習。
如果class或繼承的base class中聲明了virtual function(虛函數),編譯器會對default constructor進行擴張:
1. 為class產生一個virtual function table。
2. 為每個class object建立一個額外的point member(vptr),指向相應的virtual function table。
此外,編譯器還會對“虛函數的叫用作業”進行修改,如b.f(),會被改為:
(*(b.vptr[1])) (&b);// 1表示函數f()在vtbl中的索引值,(b.vptr[1])返回了函數f()的地址。// &b表示交給f()函數調用的this指標,這裡涉及到了name magling的知識,讀者可自行搜尋。
4. "帶有一個Virtual Base Class"的Class
虛基類(virtual Base Class)指多繼承時,某個base class可能會出現在多個繼承路徑上。為了防止該基產生多個拷貝,可將基類的繼承聲明為虛擬,這樣就只會繼承基類的一份拷貝。
雖然這樣base class只有一份拷貝,但在多態環境下,編譯器無法固定”經由某個指標類型而存取的base class中的成員”的實際位移位置,因為其實際類型是可以改變的。
cfont對這個問題的解決方案是:在derived class object的每一個virtual base class中安插一個指標__vbcX,所有經由引用或指標對virtual base class的操作都可以通過該指標來完成。
而對__vbcX的初始化,正是編譯器在default constructor中進行的擴張。
對於這4種情況以外的,且沒有聲明default constructor的class,我們說它們擁有的是trivial default constructors,實際上它們並沒有被合成出來。
所以,“任何class如果沒有定義default constructor,都會被產生一個出來”這種說法是錯誤的。
最後還有一點要注意,產生出來的nontrivial default constructor只對類對象進行初始化,或基本類型(int, double, string)的成員變數還需要程式員來顯式的進行初始化。