Default Constructor的構建操做

 在《C++ Annotated Reference Manual(ARM)[ELLIS90]》中的Section 12.1告訴咱們:"Default constructors...在須要的時候被編譯器產生出來"。
其實默認構造函數也是分爲兩類的:有用的(nontrivial )、無用的(trivial )。
所謂有用的標準也是就默認構造函數會爲咱們的類作一些初始化操做。那麼無用的就不會作任何工做,從而對咱們的類也就沒有任何意義。
因此,咱們一般所說的默認構造函數是指有用的默認構造函數,其英文名字叫nontrivial default constructor。
例以下面的代碼:
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

編譯器這樣作的理由很簡單:由於這些vtbl或vptr須要編譯器隱式(implicit)的合成出來,那麼編譯器就把合成動做放到了默認構造函數裏面。因此編譯器必須本身產生一個默認構造函數來完成這些操做。
因此若是你的類裏帶有任何virtual function,那麼編譯器會爲你合成一個默認構造函數。
(4) 若是一個類虛繼承於其它類,那麼編譯器會爲派生類合成一個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。  

總結

上面四種分析合成出的default constructor都是nontrivial default constructors,不在此狀況以內的都trivial default constructors,它們實際上並不會被編譯器合成出來
在合成的default constructors,只有base class subobjects和member class object會被初始化,全部其它的nonstatic data member,如整數、整數指針、整數數組等等都不會被編譯器初始化。
C++新手常見的兩個的 錯誤
(1)  任何class 如何沒有定義default constructor,就會被合成出來一個
(2)  編譯器合成出來的default constructor會明確設定class 內每一個data member 的默認值
相關文章
相關標籤/搜索