派生类构造与析构顺序
更新时间:2026-08-27。本文是
languages/cpp/主题入门层第 27 篇。构造时从基类到派生类,析构时从派生类到基类——这条铁律保证"基类的部分永远先就位、最后离场"。本篇把顺序讲透,并解释为什么顺序错了会出大问题。
本文要回答的问题
- 派生类构造时,先构造谁?
- 析构时谁先走?为什么顺序和构造相反?
- 基类构造函数怎么传参?
一、构造顺序:先基类,后派生类
class Base {
public:
Base() { std::cout << "Base 构造\n"; }
};
class Derived : public Base {
public:
Derived() { std::cout << "Derived 构造\n"; }
};
Derived d; // 输出:
// Base 构造
// Derived 构造先构造基类部分,再构造派生类自己的部分。为什么?逻辑很直白:派生类成员/方法可能用到基类的成员,基类不先就位,派生类一上来就踩空。C++ 保证基类永远先构造。
基类的构造函数还能在初始化列表里显式指定:
class Derived : public Base {
public:
Derived(int x) : Base(x), extra_(x) {} // 基类构造写在最前面
};Base(x) 在初始化列表里调基类的带参构造。规则:基类构造必须排在初始化列表最前(其实不写也会自动排在前面,编译器不让你乱序)。
二、析构顺序:反着来
// 同一对类,析构输出:
// Derived 析构
// Base 析构先析构派生类自己的部分,再析构基类部分。顺序和构造正好相反,道理也对称:析构派生类时,基类部分还在(派生类的清理逻辑可能还要用基类的成员);等派生类的清理做完,基类才安心退场。如果先析构基类,派生类析构时基类成员已经没了,访问就是悬垂。

三、析构顺序的一个复杂场景:成员对象
类的成员对象和基类混在一起时,完整顺序是:
- 基类构造函数
- 成员对象的构造函数(按声明顺序)
- 类自己的构造函数体
析构完全倒过来:自己 → 成员对象(逆声明序)→ 基类。
class Derived : public Base {
Member a_; // 成员对象
Member b_;
public:
Derived() : Base(), a_(), b_() {}
};
// 构造:Base → a_ → b_ → Derived 函数体
// 析构:Derived 函数体 → b_ → a_ → Base四、为什么这条规则重要
- 构造顺序反了:派生类成员用到基类数据时读到垃圾,运行期行为诡异、极难排查;
- 析构顺序反了:派生类析构里访问基类资源 = 悬垂访问,未定义行为;
- 多继承下(高手层讲),基类按声明顺序构造,析构逆序——规则一样,只是基类多了。
这条规则在性能层面也有体现:基类构造里做的初始化(开文件、连数据库),子类数量多时会重复执行,这是"继承层次太深"的性能隐忧,和对象布局篇的内容联动。
五、和 C 对比
| 对比项 | C | C++ |
|---|---|---|
| 结构体嵌套初始化 | 手动,顺序靠人 | 编译器保证基类先构造 |
| 清理顺序 | 手动管理 | 编译器保证派生类先析构 |
| 初始化失败 | 半初始化状态 | 构造异常会逐层回滚(第 19 篇的异常机制) |
C 的"组合式结构体"没有初始化顺序保证——struct Derived { struct Base b; } 你得自己记得先初始化 b。C++ 把顺序写进语言规则,错了编译期就能暴露大部分问题。
六、与本站主线衔接
一句话总结
构造顺序:基类 → 成员对象(按声明序)→ 自身;析构顺序完全反过来:自身 → 成员对象(逆声明序)→ 基类;基类构造在初始化列表最前、可用 Base(...) 传参;这条铁律保证基类部分先就位、最后离场,违反它就会出现读到垃圾值或悬垂访问的未定义行为。