關於默認構造函數

一般不少C++程序員存在兩種誤解:c++

  • 沒有定義默認構造函數的類都會被編譯器生成一個默認構造函數。
  • 編譯器生成的默認構造函數會明確初始化類中每個數據成員。

C++標準規定:若是類的設計者並未爲類定義任何構造函數,那麼會有一個默認 構造函數被暗中生成,而這個暗中生成的默認構造函數一般是不作什麼事的(無 用的),trivial下面四種狀況除外。程序員

換句話說,有如下四種狀況編譯器必須爲未聲明構造函數的類生成一個會作點事 的默認構造函數。(nontrivial constructor)咱們會看到這些默認構造函數僅「忠於編譯器」,而可能不會按 照程序員的意願效命。ide

1.包含有帶默認構造函數的對象成員的類(帶有default constructor的member class object)

若是一個class沒有任何constructor,但他含有一個member object,然後者有defautl constructor,那麼這個class的implicit函數

defautl constructor就是「nontrivial」,編譯器須要爲此class合成出一個default constructor,不過這個合成操做只有在constructor真正須要被調用時發生。設計

class Foo {public: Foo(); Foo(int);...};指針

class Bar {public: Foo foo; char str;};code

void foo_bar(){對象

   Bar bar; //Bar::foo應在此處被初始化繼承

if(str){...}ip

}

被合成的Bar default ctor內含必要的代碼,可以調用class Foo的default ctor來處理member object Bar::foo,但並不處理Bar::str。即被合成的default ctor只是爲了知足編譯器的須要(編譯器須要有個地方來初始化Bar::Foo,由於它有本身的default ctor),而不是程序的須要(初始化Bar::str是程序員的動做)。

若是程序員在ctor只顯式初始化了Bar::str,但對編譯器而言,它還須要初始化member object foo,因爲無參構造函數已經被明肯定義出來,編譯器沒辦法合成第二個。那麼這時候編譯器會怎麼作呢?

 

編譯器的行動是「若是class A內含一個或以上的member class objects,那麼classA的每個constructor必須調用每個member classs的defautl constructor,編譯器會擴張已存在的constructor。在其中安插一些代碼。使得user code在被執行以前,先調用必要的default constructor,)

若是有多個class member object都要求constructor的初始化操做,將如何呢?c++要求以「member objects在class中的聲明次序」來調用各個constructor。這一點有編譯器完成。

例如

Bar::Bar(){ str=0;      }

會被編譯器擴展爲

Bar::Bar(){ 

     foo.Foo::Foo(); //附加上的編譯器代碼

            str=0;//顯式的程序員代碼

}

2.帶有defautl constructor「的base class。

若是一個沒有定義任何構造函數的類派生自帶有默認構造函數的基類,那麼編譯器爲它定義的默認構造函數,將按照聲明順序爲之依次調用其基類的默認構造函 數。若該類沒有定義默認構造函數而定義了多個其餘構造函數,那麼編譯器擴充它的全部構造函數——加入必要的基類默認構造函數。另外,編譯器會將基類的默認構造函數代碼加在對象成員的默認構造函數代碼以前。(注:對於基類有默認構造函數的,而在子類中無顯式構造函數時,編譯器對於基類部分的數據以及數據成員不會視而不見的,並且是先基類後對象成員,對於有多個基數的,按照它們在繼承列表中的順序進行調用;固然即便你定義了其餘類型的構造函數,編譯器也會在後面幫你完成那些你沒有顯式完成的工做,它會擴充全部你編寫的構造函數,固然只是在你沒有顯式調用基類的哪一個構造函數時)。

3.帶有一個virtual function的class

另有2中狀況,也須要合成出defautl constructor。

1.     這個類本身聲明(或者繼承)了Virtual Function。

2.     這個類繼承自一個繼承串鏈,其中有一個或多個virtual base class。

無論哪種狀況,因爲缺少有user聲明的constructor,編譯器會詳細記錄合成一個defautl constructor的必要信息,如下面的程序片斷爲例;

class Widget{

  public:

       virtual void flip()=0;

     //...

};

void flip(const Wideget& widget) { widget.fiip();}

//假設Bell和whistle都派生子widget

void foo()

{

  Bell b;

Whistle w;

flip(b);

flip(w);

}

這種狀況下,編譯時,要作以下工做:

1.編譯器須要生成一個virtual function table(vtbl)並填充。

2.在class object中,一個額外的pointer member(就是vptr,指向vtbl)會被編譯器合成出來。此外虛擬調用會被替換(w.vf() => w.vprt[1])。

爲了支持這種功能,編譯器必須爲每一個w對象設置它的vptr(這是成員變量,此時須要指向合適的vtbl),所以編譯器須要在default ctor中安插一些代碼來完成這種工做。

4.帶有一個virtaul Base class的class

virtual base class的實現法在不一樣編譯器之間有極大的差別,然而,每一種實現法的共同點在於必須使virtaul base classs在其每個derived class object中的位置,可以於執行期準備穩當,例以下面這段程序代碼中:

考慮這樣的代碼:

classX { public: inti; };

classA : publicvirtualX   { public: in tj; };

classB : publicvirtualX   { public: double d; };

classC : publicA, publicB { public: int k; };

//沒法在編譯期間解析出 pa->X::i 的位置(給一個pa沒法肯定i的地址)。

void foo( constA* pa ) { pa->i = 1024; }

main() {

   foo( new A );

   foo( new C );

   // ...

}

因爲pa的真正類型不肯定,因此某些編譯器會記錄一個指針例(如 __vbcX)來記錄X,而後經過這個指針來定位pa指向的i。上述

void foo( constA* pa ) { pa->i = 1024; }

變成了:

void foo( constA* pa ) { pa-> __vbcX ->i = 1024; }

所以,__vbcX這個指針(執行virtual base class)須要在object構造期間設置好。因而編譯器須要一個default ctor來完成這個工做。

相關文章
相關標籤/搜索