C++ 建構函數的初始化列表 是區分「C with Classes」與現代 C++ 的關鍵分水嶺。它不僅關係到效能(避免默認構造+賦值),更是 const、引用、無默認構造類型成員**唯一合法初始化路徑**。本文從兩階段構造機制、記憶體佈局、編譯器行為、最佳實踐四個維度,徹底講透初始化列表。
struct Foo {
std::string name;
int id;
Foo(std::string s, int i) : name(s), id(i) {} // 初始化列表
};
{ ... } 後執行賦值、邏輯代碼。此時成員**已完整構造**,對成員操作屬於**賦值/修改**,而非初始化。struct Test1 {
Test1() { std::cout << "Test1()\n"; }
Test1(const Test1&) { std::cout << "Test1(copy)\n"; }
Test1& operator=(const Test1&) { std::cout << "Test1(assign)\n"; return *this; }
int a;
};
struct Test2_Bad {
Test1 t1;
Test2_Bad(Test1& src) { t1 = src; } // 體內賦值
};
struct Test2_Good {
Test1 t1;
Test2_Good(Test1& src) : t1(src) {} // 初始化列表直接拷貝構造
};
int main() {
Test1 src;
std::cout << "--- Bad ---\n";
Test2_Bad b(src); // 輸出:Test1() → Test1(assign)
std::cout << "--- Good ---\n";
Test2_Good g(src); // 輸出:Test1(copy)
}
結論:初始化列表**少一次默認構造 + 一次賦值**,對資源密集型類(std::vector、std::string、智慧指標、資源句柄)效能提升顯著。
| 場景 | 原因 | 錯誤示例 | 正確寫法 |
|---|---|---|---|
const 成員 | 只能初始化,不能賦值 | const int x; Foo() { x = 1; } | Foo() : x(1) {} |
| 引用成員 | 宣告即綁定,不可重新綁定 | int& ref; Foo(int& r) { ref = r; } | Foo(int& r) : ref(r) {} |
| 無默認構造的類型成員 | 體內會先嘗試默認構造 → 編譯失敗 | struct A { A(int); }; struct B { A a; B() {} } | B(int i) : a(i) {} |
struct OrderDemo {
int a;
int b;
// 列表寫 b(x), a(b) 但宣告 a 在前
OrderDemo(int x) : b(x), a(b) {} // 警告:a 先初始化,此時 b 未初始化 → a 值不定
};
鐵律:成員**按類中宣告順序初始化**,與初始化列表書寫順序無關。**最佳實踐:列表順序與宣告順序保持一致**,開啟 -Wreorder (GCC/Clang) 或 /Wall (MSVC) 讓編譯器強制檢查。
struct Base { Base(int) { std::cout << "Base\n"; } };
struct Member { Member(int) { std::cout << "Member\n"; } };
struct Derived : Base {
Member m1, m2;
Derived(int x) : m2(x), Base(x), m1(x) {} // 列表順序隨意
// 實際順序:Base → m1 → m2 → Derived 體
};
struct S { int x = 42; }; // C++11 可省略列表中 x(42)。Foo() : Foo(0) {} 避免重複邏輯。using Base::Base; 自動帶入基類建構函數。struct P { int x,y; }; P p{1,2}; 無需建構函數。| 錯誤模式 | 後果 | 檢測工具 |
|---|---|---|
| 列表順序與宣告順序不一致 | 讀取未初始化成員、邏輯 Bug | -Wreorder, clang-tidy bugprone-member-init-order |
| 基類未在列表顯式初始化且無默認構造 | 編譯錯誤 | 編譯器直接報錯 |
| 引用/const 成員未在列表初始化 | 編譯錯誤 | 編譯器直接報錯 |
| 虛函數在建構函數中調用 | 靜態綁定到基類版本(衍生類尚未構造) | clang-tidy bugprone-virtual-call-in-constructor |
| 建構函數拋異常導致資源洩漏 | 已構造成員自動析構,但手動分配資源需 RAII 守護 | 代碼審查、cppcheck |
把這份清單印出來貼在 IDE 旁,每次寫 ClassName() : 時心裡過一遍——現代 C++ 的優雅,從正確的初始化列表開始。