成员对象必须在构造函数初始化列表中初始化,因C++禁止在函数体内赋值初始化const、引用或无默认构造函数的成员;声明时未调用构造函数,真正初始化发生于进入构造函数前。

成员对象必须在构造函数初始化列表里初始化
类的组合关系本质是“拥有”另一个类的对象作为成员,而这个成员对象一旦定义,就必须完成初始化——它没有默认的“未初始化”状态。C++ 不允许在构造函数体内部用赋值方式初始化非静态 const 成员、引用成员,或任何没有默认构造函数的类类型成员。MyClass m_obj; 这样的声明本身不调用构造函数,真正初始化发生在进入构造函数之前,只能靠初始化列表。
- 错误写法:
m_obj = MyClass(42);在构造函数体内执行 → 实际触发的是赋值操作(需operator=),而非初始化;若MyClass没有默认构造函数,编译直接失败 - 正确写法:
MyClass(int x) : m_obj(x), m_ref(m_data) {}—— 所有成员对象、引用、const 成员都必须列在这里 - 顺序无关括号内书写顺序,只按类中声明顺序初始化;若初始化顺序和声明顺序不一致,编译器会警告(如 GCC 的
-Wreorder)
没有默认构造函数的成员必须显式初始化
如果被组合的类(比如 Engine)只提供了带参构造函数,那它的对象就无法“自动默认构造”。这时候你不能省略初始化列表中的对应项,否则编译器找不到匹配的构造方式,报错类似:no matching constructor for initialization of 'Engine'。
- 常见场景:封装第三方库对象、自定义资源句柄(如
FileHandle、DatabaseConnection),它们通常禁用默认构造以避免无效状态 - 解决方法:在初始化列表中明确传参,例如:
Car() : engine_(95 /* octane */), wheels_{Wheel(), Wheel(), Wheel(), Wheel()} {} - 注意数组成员(如
Wheel wheels_[4];):若Wheel无默认构造,则整个数组声明非法;应改用std::array<wheel></wheel>或std::vector配合初始化列表构造
初始化列表里调用成员函数是危险的
在初始化列表中,对象的基类和成员尚未完全构造完毕,此时调用虚函数会绑定到当前类(而非派生类)的实现,调用普通成员函数则可能访问未初始化的其他成员——行为未定义。
- 错误示例:
Derived() : base_(initHelper()) {},其中initHelper()内部访问了另一个尚未初始化的成员data_ - 安全做法:把复杂逻辑拆到 private static 函数,确保它只依赖参数或全局/静态状态;或者推迟到构造函数体中执行(前提是目标成员已由初始化列表正确构造)
- 尤其警惕 lambda 捕获
this后在初始化列表中调用——this此时指向的对象处于“半构造”状态
聚合类 / C++20 指定初始化器不适用于组合对象初始化
像 struct Point { int x, y; }; 这种聚合类可以用 {1, 2} 初始化,但一旦加入私有成员、用户定义构造函数或基类,就不再是聚合类。组合关系中的成员对象几乎总是非聚合的(因为要封装、控制生命周期),所以不能指望用大括号初始化绕过初始化列表。
立即学习“C++免费学习笔记(深入)”;
-
class Engine { public: Engine(int cc); };→Engine e{2000};合法,但Car c{{2000}};不行,除非Car显式定义了接受Engine初始化的构造函数 - C++20 指定初始化器(
Car c{.engine_=Engine(2000)};)仅对聚合类或带相应设计的类有效,对常规组合类无作用 - 别试图用
std::make_unique<t>(...)</t>替代初始化列表——那是运行时动态分配,和栈上成员对象的构造阶段无关
最常被忽略的一点:初始化列表不是可选项,而是构造语义的一部分。哪怕成员类型恰好有默认构造函数,显式写出来也更清晰,且能避免未来因类型变更(比如删掉默认构造)导致的隐性编译失败。











