C++對象的構造

C++中通過用戶自定義類建立對象時,需要調用構造函數,這裏包含默認構造函數、複製構造函數和自定義構造函數。其中自定義構造函數,按照函數重載機制進行匹配調用,與普通重載函數調用類似。因此,這裏討論的是默認構造函數和複製構造函數,因爲這兩種構造函數如果用戶不顯式定義,會在特定情況下被編譯器合成出來。編譯器合成的規則並不明顯,C++標準做的說明只是“在需要的時候合成”。

默認構造函數(default constructor)

默認構造函數就是不需要參數的構造函數或者參數都帶有默認值的構造函數。
理解默認構造函數需要從程序語義層面和編譯器層面(C++實現)兩個方面來入手。考慮如下代碼:

class A{
    int a;
    float b;
};

A objA;
...

從程序語義層面上看,變量objA需要一個默認構造函數,並且最好能初始化成員變量的值爲0,但是由於沒有明確定義默認構造函數,因此編譯器會是否合成出這個默認構造函數取決於編譯器層面上能否進行編譯,因此就衍生出了trivial和non-trivial默認構造函數的概念,如果是一個non-trivial性質的,編譯器就會合成;否則就不會合成。
編譯器總共支持四種non-trivial的情形,出現這些情形中的任何一個或多個都會當做non-trivial從而合成出默認構造函數:
1. 類的成員對象存在默認構造函數
2. 類的基類存在默認構造函數
3. 存在虛函數的類
4. 存在虛基類的類

類的成員對象含有默認構造函數

編譯器合成的版本的唯一功能就是調用所有這些對象的默認構造函數,這就是編譯器必須要滿足的要求,其他的初始化操作都不會施行。

class A{
public: A():val(0){}
private: int val;
};
class D : A{
    A a;
    int dval;
};

D objD;

對象 objD 會被調用編譯器合成的構造函數,用來唯一功能就是調用成員對象a的默認構造函數。對於用戶定義的其他構造函數,如果沒有初始化相應的帶有默認構造函數的成員對象,編譯器也會合成相應的初始化調用語句插入到用戶定義的構造函數內最開始處。如果存在多個需要初始化的成員對象,那麼會按照類中成員對象聲明的順序來依次調用每個成員的構造函數。

類的基類存在默認構造函數

如果基類存在用戶定義的默認構造函數,而子類沒有定義默認構造函數時,編譯器會合成non-trivial的默認構造函數。對於多個基類的繼承,會按照聲明順序依次調用父類的默認構造函數。如果存在用戶定義的構造函數而沒有默認構造函數,那麼會被編譯器插入到所有這些構造函數最開始初。另外,如果同時存在有默認構造函數的成員對象,會在調用完父類的構造函數之後,再調用成員對象的構造函數。

存在虛函數的類

對於存在多態性質的類,根據C++多態的實現方式,編譯器會爲每個對象插入一個虛函數表指針(vptr),因此默認構造函數必須進行這個vptr成員的初始化,放置虛函數表的地址,此時non-trivial默認構造函數必須被合成,插入相應代碼來完成這些工作。

class B{
public:
    virtual void fun();
    ...
};
class A:B{
public:
    virtual void fun();
    ...
};

B * pb = new A;
pb->fun();

上述用new創建類A的對象時會調用默認構造函數,由於多態,默認構造函數被編譯器合成後初始化虛函數表指針vptr,最終的調用操作會被解析到:

(pb->vptr[1])(pb); //假設虛函數fun爲聲明的第一個虛函數,地址在在虛函數表中第二項

存在虛基類的類

與前面多態性質的類對象存在vptr類似,虛基類的支持雖然不同編譯器有差異,但是共通點都是需要解決虛基類在派生類中的位置能在執行期明確,爲了實現這個需求,要麼繼續使用vptr,要麼使用虛基類指針vbc,總之都需要編譯器爲每個對象合成出新的指針成員,這樣就和前面多態的情況類似,需要構造默認構造函數,安插對合成的指針成員的初始化操作。

除了上述四種情況外,其他情況下,如果用戶沒有定義默認構造函數,編譯器並不會合成,因此
1. 不是所有默認構造函數沒有定義的類編譯器都會合成
2. 編譯器合成的默認構造函數不會初始化所有數據成員

複製構造函數(copy constructor)

複製構造函數的調用有三個地方
1. 顯式調用
2. 非引用的傳遞參數
3. 非引用的函數返回

最基本的複製構造就是基於bitwise語義,也就是基本變量的賦值操作。編譯器是否合成複製構造函數也是看trivial和non-trivial,而這取決於類是否表現出bitwise拷貝語義,與前面的默認構造函數類似,存在以下四種情況不表現出bitwise拷貝語義,從而需要合成:
1. 成員對象存在複製構造函數
2. 基類存在複製構造函數
3. 多態性的虛函數
4. 帶有虛基類

可以很明顯看出,這四種與前面默認構造函數的non-trivial完全相同。

發佈了158 篇原創文章 · 獲贊 42 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章