編譯器只在必要時合成預設建構函數 (implicit nontrivial default constructor)。若類別不屬於以下四類,編譯器不會合成,類別擁有「隱式無效預設建構函數」 (implicit trivial default constructor),實際不存在程式碼。
| 情況 | 合成目的 | 插入操作 |
|---|---|---|
| 1. 成員含有預設建構函數的類別物件 | 確保成員子物件初始化 | 按宣告順序呼叫成員的預設建構函數 |
| 2. 基類含有預設建構函數 | 確保基類子物件初始化 | 呼叫基類預設建構函數 |
| 3. 類別宣告/繼承 virtual 函數 | 建立虛函數表指標 (vptr) | 設定 vptr 指向正確 vtbl |
| 4. 類別有 virtual 基類 | 執行期決定 virtual 基類位址 | 設定 virtual 基類指標/偏移 |
class X { public: X() { cout << "X::X()\n"; } int m; };
class Xs { X mX; int mN; }; // 無使用者建構函數
int main() { Xs xs; } // 輸出: X::X()
// 合成偽碼: inline Xs::Xs() { mX.X::X(); } // mN 未初始化
若使用者自訂建構函數 Xs() { mN=1; },編譯器擴張而非重新合成:
inline Xs::Xs() { mX.X::X(); cout<<"Xs::Xs()\n"; mN=1; } // 成員初始化在使用者碼之前
class X { public: X() { cout<<"X::X()\n"; } };
class XX : public X { int n; }; // 無建構函數
// 合成: inline XX::XX() { X::X(); }
class XXX : public XX { }; // 繼承鏈,合成呼叫 XX::XX() 進而呼叫 X::X()
使用者提供建構函數時同樣擴張插入基類呼叫。
編譯器為每個多態類別產生 虛函數表 (vtbl),並在每個物件植入 vptr。
class X { virtual void f(); virtual ~X(); };
// 合成建構函數偽碼:
inline X::X() { this->vptr = &X::vtbl; }
繼承類別合成建構函數會設定 vptr 指向 派生類別的 vtbl,實現多態。
Virtual 基類在記憶體中位置不固定,需執行期透過 vptr 或額外指標定址。合成建構函數負責初始化這些內部指標。
class X { int m; };
class X1 : virtual public X { int m1; };
class X2 : virtual public X { int m2; };
class XX : public X1, public X2 { int mxx; };
// XX 物件佈局包含兩個 vptr (分別指向 X1, X2 的虛基類資訊)
// 合成建構函數初始化這些指標
= default (C++11) 明確要求編譯器生成預設行為,避免隱式合成驚喜。理解編譯器合成預設建構函數的「四大條件」與「擴張而非重寫」機制,是掌握 C++ 物件模型、避免未初始化變數 Bug、優化建構效能的關鍵。