class Foo { public: int val; Foo *pnext; }; void f00_bar() { Foo bar; //程序要求bar's members都被清爲零,不是編譯器須要 if(bar.val || bar.pnext) //...do something }
編譯器並不會爲上述代碼生成一個默認的構造函數,只有在下面四種狀況下,編譯器纔會生成默認構造函數(nontrivial default constructor):程序員
(1) 若是一個類裏面某個成員對象有nontrivial default constructor,編譯器就會爲咱們的類產生nontrivial default constructor數組
class Foo { public: Foo(); Foo(int); ... }; class Bar { public: Foo foo; char *str; }; void foo_bar() { Bar bar; if(str) {} ... }
被合成的Bar default constructor 內含必要的代碼,可以調用class Foo的default constructor 來處理member object Bar::foo,但它並不產生任何代碼來初始化Bar::str。被合成的default constructor看起來可能像這樣:bash
inline Bar::Bar() { //C++僞代碼 foo.Foo::Foo(); }
若是程序員定義了default constrcutor函數
Bar::Bar() { str = 0; }
如今程序員的需求知足了,可是編譯器還須要初始化member object foo。編譯器會擴張現存的constructor,在其中安插一些代碼,使得user code在被執行前,先調用必要的default constructors。設計
Bar::Bar() { foo.Foo::Foo(); //附加上的compiler code str = 0; }
若是有多個class member objects都要求constructor初始化操做,C++語言要求以 「member objects在clsss中的聲明次序」來調用各個constructor。指針
class Dopey {public: Dopey(); ...}; class Sneezy {public: Sneezy(int); Sneezy(); ...}; class Bashful {public: Bashful(); ...}; class Snow_White{ public: Dopey dopey; Sneezy Sneezy; Bashful bashful; .... private: int mumbae; };
若是Snow_White沒有定義default constructor,就會有一個nontrivial constructor被合成出來,一次調用Dopey、Sneezy、Bashful的default constructor。若是定義了下面這樣的default constructor:code
Snow_White::Snow_White() : sneezy(1024) { mumble = 2048; } 他會被擴展爲: Snow_White::Snow_White() { //按照聲明的順序調用其constructor dopey.Dopey::Dopey(); sneezy.Sneezy::Sneezy(); bashful.Bashful::Bashful(); mumble = 2048; }
(2) 若是一個派生類的基類有nontrivial default constructor,那麼編譯器會爲派生類合成一個nontrivial default constructor對象
編譯器這樣的理由是:由於派生類被合成時須要顯式調用基類的默認構造函數。blog
若是設計者提供多個constructor,但其中都沒有調用default constructor,編譯器不會合成一個新的default constructor,它會擴張現有的每個constructor,將「用以調用全部必要之default constructor」的程序代碼加進去。繼承
(3) 若是一個類帶有一個Virtual Function,那麼編譯器會爲派生類合成一個nontrivial default constructor
編譯器這樣作的理由和(3)相似:由於虛繼承須要維護一個相似指針同樣,能夠動態的決定內存地址的東西(不一樣編譯器對虛繼承的實現不全相同)。
class X {public: int i; }; class A : public virtual X {public: int j; }; class B : public virtual X {public: double d; }; class C : public A, public B {public: int k; }; //沒法在編譯時期決定出pa->X::i 的位置 void foo(const A* pa) { pa->i = 1024; } mian() { foo(new A); foo(new C); ... }
編譯器沒法固定住foo()之中「經由pa而存取的X::i」的實際偏移量,覺得pa的真正類型能夠改變。foo()能夠被改寫以下:
void foo(const A* pa) { pa->_vbcX->i = 1024; }
其中,_vbcX表示編譯器所產生的指針,指向virtual base class X。
總結