虚析构函数必须声明为virtual,否则通过基类指针删除派生类对象时仅调用基类析构,导致资源泄漏;纯虚析构需声明并定义,用于强制抽象且保证析构链完整。

虚析构函数必须加 virtual,否则多态删除会漏掉派生类析构
如果你用 Base* 指针指向 Derived 对象,然后 delete 它,而 Base::~Base() 没有 virtual,编译器只会调用 Base 的析构函数——Derived 的析构逻辑完全不执行。这会导致资源泄漏(比如没关文件、没释放内存、没解锁互斥量)。这不是运行时错误,而是静默失效,极难排查。
正确做法只有一条:只要类设计为基类、且预期被多态使用(尤其是通过指针/引用管理生命周期),它的析构函数就必须声明为 virtual。
-
class Base { public: virtual ~Base() = default; };—— 最简安全写法,推荐用于非抽象基类 - 不要写
~Base() {}或~Base() = delete;,前者非虚,后者禁止继承 - 即使基类没有资源要清理,也别省略
virtual—— 派生类可能有,而你无法预判所有子类行为
纯虚析构函数不是为了“纯虚”,而是为了强制抽象 + 保全析构链
声明 virtual ~Base() = 0; 的真实目的,是让类不可实例化(即成为抽象类),同时仍保证析构链完整。但它有个反直觉规则:**必须提供定义体**,否则链接失败。
常见错误是只声明不定义,比如:
class Base {
public:
virtual ~Base() = 0; // ❌ 链接时报错:undefined reference to `Base::~Base()`
};
正确写法是声明 + 类外定义(哪怕空):
class Base {
public:
virtual ~Base() = 0;
};
Base::~Base() {} // ✅ 必须存在,可为空
- 定义可以放在头文件里(内联),也可放在
.cpp中;但不能省略 - 纯虚析构函数不阻止派生类调用它——相反,派生类析构末尾会自动插入对它的调用
- 如果基类本就含其他纯虚函数(如
virtual void draw() = 0;),再加纯虚析构只是锦上添花;若仅靠析构函数变抽象,则这是唯一手段
= default 和 = 0 语义完全不同,混用会编译失败
virtual ~Base() = default; 是显式要求编译器生成默认实现,适用于可实例化的虚析构;virtual ~Base() = 0; 是声明纯虚,强制抽象,二者互斥。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
下面这些写法全部非法:
-
virtual ~Base() = 0 = default;—— 语法错误 -
virtual ~Base() = 0; virtual ~Base() = default;—— 重复定义 - 在类内写
virtual ~Base() = 0 {}—— C++ 标准禁止纯虚函数带函数体(除析构函数外)
记住:只有析构函数能既 = 0 又需要定义体;其他纯虚函数绝对不能有函数体。
性能与 ABI 兼容性:虚析构函数几乎零开销,但会影响类的布局
加 virtual 会让类隐含虚表指针(通常 8 字节),哪怕它只有析构函数一个虚函数。这对内存敏感场景(如大量小对象)有轻微影响,但远不如漏掉析构导致的崩溃或泄漏严重。
更隐蔽的影响是 ABI 兼容性:
- 一旦发布库的基类加了
virtual析构,就不能再删掉——否则下游链接失败或析构行为错乱 - 从
virtual ~Base() = default;改成virtual ~Base() = 0;是二进制兼容的(都含虚表),但语义上禁止实例化,需同步更新所有用户代码 - 如果基类已有虚函数,加虚析构不会新增虚表,只增加一个虚表项,成本极低
真正容易被忽略的是:**虚析构函数的访问控制(public/protected)必须和 delete 行为匹配**。如果析构是 protected,那只能通过 shared_ptr 或友元类销毁,直接 delete 会编译报错——这个限制常被当成“防止误删”,但实际增加了使用门槛,除非有明确设计意图,否则保持 public 最稳妥。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










