前言
C++新手一般的兩個常見誤解:
1、任何class,如果沒有定義default constructor,就會被合成出來一個;
2、編譯器合成出來的default constructor會顯式設定“class內每個data member的默認值”。
在沒有讀《深度探索C++對象模型》之前,我依然以爲第一點是成立的。直到看了這本書之後,才明瞭編譯器及其在需要的時候合成的default constructor函數在我們背後做了哪些事情。
這裏“需要的時候”指的是以下四種情況:
①帶有default constructor的member class object;
②帶有default constructor的base class;
③帶有一個virtual function的class;
④帶有一個virtual base class的class。
只有這四種情況,編譯器會爲未聲明constructor的class合成一個default constructor,這些被合成出來的constructor稱爲implicit nontrivial default constructor,它們只能滿足編譯器(而非程序)的需要;而沒有存在這四種情況而又沒有聲明任何constructor的class,稱這些class擁有的是implicit trivial default constructors,實際上它們並不會被合成出來。
下面就來分析這四種情況。
帶有default constructor的member class object
如果一個class沒有任何constructor,但它內含一個member object,且該member object有default constructor,那麼編譯器就會爲這個class合成一個implicit nontrivial default constructor,這個合成操作只有在constructor真正需要被調用時纔會發生。
// Foo是一個含有default constructor的class
class Foo {
public:
Foo();
Foo(int);
// ...
};
class Bar {
public:
Foo foo; // member object
char* str;
// ...
};
void foo_bar {
Bar bar; // Bar::foo必須在此處初始化
if (str) {
// ...
}
}
在上面的代碼中,定義一個Bar class object時,編譯器必須通過調用Foo的default constructor來初始化Bar的member object(foo),但是我們並沒有爲Bar提供default constructor,因此,編譯器會爲我們合成一個default constructor,被合成的default constructor看起來可能像這樣:// 編譯器爲class Bar合成的default constructor僞代碼
inline Bar::Bar() {
foo.Foo::foo(); //調用class Foo的default constructor
}
注意,將Bar::foo初始化是編譯器的事,因此會合成default constructor,只滿足編譯器的需要,而將Bar::str初始化則是程序員的事。現在讓我們以同樣的方式考慮另外一件事,就是當程序員爲class Bar提供了default constructor,編譯器該怎麼辦?如下:
// Foo是一個含有default constructor的class
class Foo {
public:
Foo();
Foo(int);
// ...
};
// Bar也含有default constructor
class Bar {
public:
Bar() {
str = 0;
}
Foo foo; // member object
char* str;
// ...
};
上面的代碼中,我們自定義的default constructor並沒有初始化member object(foo),那麼它也就不能被初始化;然而由於class Bar已經存在了default constructor,自然編譯器也不可能爲我們去合成一個default constructor,因此foo的初始化依然不能解決。
實際上,雖然編譯器不能合成default constructor,但爲了解決問題,它會在我們提供的default constructor中進行一些小動作。是的,沒錯,編譯器會我們的在default constructor中安插一些代碼。可能的改動如下:
// 改動後的default constructor僞代碼
Bar::Bar() {
foo.Foo::foo(); //調用class Foo的default constructor
str = 0;
}
當然,同理:如果程序中自定義了constructor而沒定義default constructor,根據上述的分析,編譯器不會爲我們合成default constructor,而是對每一個自定義的constructor進行改動,改動方法類似。
以上就是class不存在default constructor時,編譯器必須合成default constructor的第一種情況。帶有default constructor的base class
如果一個沒有任何constructor的class派生自一個帶有default constructor的base class,那麼編譯器會爲這個derivied class合成default constructor。因爲在定義一個derivied class object時,會先調用base class 的default constructor(當然,在destructor時順序是相反的)。
當derivied class中定義了constructor或default constructor,編譯器不會合成default constructor,但會有必要的對每一個constructor進行改動,使其能夠調用到base class的default constructor。
以上就是編譯器爲class合成default constructor的第二種情況。
總結前兩種情況就是:只要滿足1、2的情況下,若class沒有任何constructor和default constructor,編譯器都會合成一個default constructor;若存在constructor或default constructor,則編譯器會進行改動安插代碼。
帶有一個virtual function的class
分兩種情況:
1、class聲明或繼承了一個virtual function;
2、class派生自一個繼承串鏈,其中有一個或多個virtual base classes。
不管哪一種情況,由於virtual的存在,就會有vtbl(虛表)和vptr(指向vtbl的指針)的存在,在上一篇《深度探索C++對象模型》中講到vtbl和vptr的設定和重置在編譯器件由class的constructor、destructor和copy assignment運算符自動完成,但是由於沒有default constructor,因此編譯器會合成一個default constructor,以完成vtbl和vptr的構造和設定。
在編譯期間會發生(但不限於此):
1、產生一個vtbl,內含class的virtual function地址;
2、產生一個額外的指針vptr,指向class vtbl。
以上就是編譯器爲class合成default constructor的第三種情況。
帶有一個virtual base class的class
如下:
class X {
public:
int i;
};
class A : public virtual X{
public:
int j;
};
class B : public virtual X {
public:
int d;
};
class C : public A, public B {
public:
int k;
};
class X是爲一個virtual base class,當然但凡是遇到virtual的class,都會存在有vtbl和vptr。因此必須要靠編譯器來合成default constructor,以完成一系列複雜的操作。
以上就是編譯器爲class合成default constructor的第四種情況。
綜上所述
在合成的default constructor中,只有base class object和member class object會被初始化,其他的所有的nonstatic data member(諸如int、int*、int[])等都不會被初始化,因爲這都不是編譯器必須的,而是由程序員負責。
參考資料
[1] 深度探索C++對象模型,[美]Stanley B. Lippman著,侯捷譯;