C++的對象構造順序

最近要開始準備筆面試了,找幾份筆試題,覺得挺吃驚的,本來覺得基礎看的差不多了,這下又弄的我暈乎乎的。

筆面試出現的那些題目有的考的真細啊, 一些妖嬈的寫法要問運行結果,這些東西書上提的太少,若不是自己嘗試過一時間還怎麼不知道怎麼下手了。

我是覺得沒必要沒完沒了的浪費時間去琢磨這些,C++本來不好掌握,如果精力多的話,花時間來規範書寫、放棄使用糟糕方法更好,而不是一個個去琢磨。

總之,人家非得這麼考,那我也沒辦法了。這篇用來備忘。

------------------------------------------------

C++類單繼承情況下構造和析構順序是書上提及最多的,父類構造總是先於子類、子類析構總是先於父類。

例如:

class A 
{
public:
    A() {cout << "A" << endl;}
    A(int a) {cout << "A" << a << endl;}
};

class B : public A
{
public:
    B() {cout << "B" << endl;}
};

在實例化B b; 或者 new B;的時候,輸出結果是A \n B \n 

再定義C繼承B,構造C的時候輸出“C\n”,那麼輸出結果就是A\n B\n C\n 

這個沒什麼特別要說的。


多繼承的情況下,例如:

class D
{
public:
    D() {cout << "D" << endl;}
    D(int a) {cout << "D" << a << endl;}
};

class E : public D, public A
{
public:
    E() {cout << "E" << endl;}
};
此時的構造順序在滿足單繼承構造順序要求的情況下,出現了兩個同級父類的構造順序問題。

修改繼承循序,會發現結果亦隨之改變。在有多重繼承下,對於同級父類,構造順序是根據繼承列表從左往右的。


類中包含類對象的情況下的構造順序,例如:

class C : public B 
{
public:
    D d; 
    A a;
};

構造順序爲先A,再B,然後對於C來說發生了變化。

這裏需要先對類屬性d、a進行構造,然後才能進行本身的構造。所以輸出是A B D A C (換行符人工回收了..)

推測是因爲構造C需要知道分配內存的大小,這又依賴於D和A的大小,所以需要先對這兩個對象進行內存分配,然後才能確定C的大小(因爲調用構造函數前需要先進行內存分配)

而對於這裏D和A的構造順序,則是由他們定義的順序決定的。靠前的會先被構造(但我不確定C++規範是否明確定義了這點)。


現在擴展一下該問題:

class C : public B 
{
public:
    D d(5); 
    A a;
};

顯然這種寫法會帶來一個編譯錯誤,但大多數情況下我們面對的都不是一個只包含默認構造函數的類,甚至是沒有默認構造函數的類,那麼如此定義是否會造成編譯錯誤以及如何進行初始化呢?

比如類D的定義爲:

class D
{
public:
//    D() {cout << "D" << endl;}
    D(int a) {cout << "D" << a << endl;}
};

答案是類內聲明D d; 並不會帶來編譯錯誤,而在函數中執行D d; 會產生編譯錯誤。

雖然寫法一樣,但這應該是兩個不同的概念,前者僅僅作爲聲明,並不執行具體的實例化操作,而後者將對其進行實例化。

對應的初始化方法我知道的只有通過構造函數初始化列表這一種。但需要注意的是,構造函數的初始化列表的順序並不表示實際上初始化的順序,這可能會在存在依賴的初始化行爲裏稱爲一個隱藏的較好的BUG,實際初始化順序是由定義的順序決定的,即:

class C : public B 
{
public:
    C():a(5),d(5) {cout << "C" << endl;}
    D d; 
    A a;
};

這種情況下的會先構造D,後構造A。


多訪問區段下的構造成員順序:

class C : public B 
{
public:
    D d3;
public:
    C():a(5),d(0),d1(1),d2(2),d3(3) {cout << "C" << endl;}
private:
    A a;
    D d;
public:
    D d1;
    D d2;
};

例如對於上述代碼,不斷交換區段位置和訪問權限,始終都是按照從上到下的順序進行構造。


存在靜態類成員

class D
{
public:
    D(int a) {cout << "D" << a << endl;}
};

class C : public B 
{
public:
    C():a(5){cout << "C" << endl;}
    static D d; 
    A a;
}; D C::d(5);

對於這樣的情況,不在初始化列表裏添加d(int) 編譯是可以通過的(相對的,沒有static的時候是不能通過編譯的)。

推測應該是因爲C++對靜態成員的內存管理方式和普通成員不同,它並不計入所在對象所佔空間(即sizeof得到的結果),而是分配在全局內存上。

初始化需要放在類聲明之外進行,關於靜態對象初始化就是一個比較麻煩的問題了。不同編譯單元的靜態成員初始化順序未知,而本編譯單元好像是隨着全局變量一起初始化還是在第一次調用的時候初始化...這裏搞不清了,日常很少這麼用。

總之這裏進行初始化的時候比所有的類都要早進行,應該是被放進全局初始化列表了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章