1. 程式人生 > >Oshyn —— 樂而學,學而樂

Oshyn —— 樂而學,學而樂

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完全相同。