0

0

C++中为什么析构函数必须是虚函数? (多态下的内存泄漏)

冰火之心

冰火之心

发布时间:2026-02-11 02:33:28

|

874人浏览过

|

来源于php中文网

原创

基类析构函数必须声明为virtual,否则通过基类指针delete派生类对象时仅调用基类析构,导致派生类资源泄漏;纯虚析构也需提供定义,智能指针中unique_ptr仍依赖虚析构,shared_ptr是特例但不可依赖。

c++中为什么析构函数必须是虚函数? (多态下的内存泄漏)

基类指针 delete 派生类对象时,不加 virtual 会跳过派生类析构

核心问题不是“析构函数该不该虚”,而是:当用 Base* 指向 Derived 对象并调用 delete 时,若 Base::~Base() 不是虚函数,编译器只调用 Base 的析构函数,完全跳过 Derived::~Derived() —— 这意味着派生类中所有资源(如 new 出的内存、打开的文件句柄、锁等)都不会被释放。

常见错误现象:
- 程序运行无 crash,但 Valgrind 报告“definitely lost”内存块
- 对象里有 std::vectorstd::string 成员?它们的内部缓冲区可能没被回收(虽然标准库实现通常有保护,但自定义资源一定出问题)
- 多线程下出现诡异的 double-free 或 use-after-free,其实是上层逻辑误以为资源已清理

  • 只有通过基类指针/引用 delete 对象时才触发这个问题;直接 Derived d; delete &d; 或栈对象自动析构不受影响
  • 只要类设计为“可能被继承 + 可能被多态删除”,就必须把析构函数声明为 virtual
  • 虚析构函数本身不增加运行时开销,但会让类失去 triviality(影响 std::is_trivially_destructible 判断)

纯虚析构函数也能防止内存泄漏,但必须提供定义

如果基类本就不该被实例化(比如抽象接口类),可以写成纯虚析构:virtual ~Base() = 0;。但这只是语法上禁止构造,不代表能省略实现——链接器会报错 undefined reference to 'Base::~Base()',因为即使纯虚,析构函数仍需在派生类析构链中被调用。

  • 必须在类外提供定义:Base::~Base() {}(哪怕空实现)
  • 纯虚析构和普通虚析构在多态 delete 行为上完全一致,区别仅在于是否强制派生
  • 不要为了“看起来更抽象”而滥用纯虚析构;若基类允许实例化,就用普通虚析构

现代 C++ 中,智能指针不会绕过虚析构要求

有人误以为用 std::unique_ptr 就安全了,其实不然。智能指针的默认删除器仍是基于静态类型调用析构函数。除非显式传入自定义删除器,否则它和裸指针 delete 面临完全相同的问题。

示例:

IBM Watson
IBM Watson

IBM Watson文字转语音

下载

立即学习C++免费学习笔记(深入)”;

std::unique_ptr ptr = std::make_unique();
// 若 Base::~Base() 不是 virtual,这里仍只调用 Base::~Base()
  • std::shared_ptr 是例外:它在构造时捕获了实际类型信息,所以即使基类析构非虚,Derived 析构也会被正确调用(但这是靠控制块实现的特例,不是语言规则)
  • 别依赖 shared_ptr 的这个行为来规避虚析构;它不改变裸指针或 unique_ptr 的语义,也不解决其他 RAII 类型(如自定义 handle 类)的资源泄漏
  • 虚析构是面向多态删除的通用契约,和用什么指针管理无关

什么时候可以不写虚析构?

只有两种情况真正安全:
- 类明确设计为“不可继承”(加 final 关键字)
- 类绝不会被多态删除(即:永远不会用基类指针指向派生对象后调用 delete

注意:
- final 是编译期保证,比文档注释可靠得多
- “绝不会”这种判断容易出错,尤其在大型项目或 API 设计中;宁可多加 virtual,也不要赌使用者不会误用
- 模板基类(如 template class Base { ... };)通常不需要虚析构,因为模板实例化后一般不用于多态场景

最容易被忽略的一点:析构函数的访问控制(public/protected)和虚性无关,但若设为 private,连派生类都无法调用,直接编译失败 —— 虚只是解决“调哪个”,不是解决“能不能调”。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

668

2023.08.02

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

20

2025.11.27

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

193

2025.08.29

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

104

2025.10.23

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

1367

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

318

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2202

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

35

2026.01.19

包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法
包子漫画网页版入口与全集阅读指南_正版免费漫画快速访问方法

本专题汇总了包子漫画官网和网页版入口,提供最新章节抢先看方法、正版免费阅读指南,以及稳定访问方式,帮助用户快速直达包子漫画页面,无广告畅享全集漫画内容。

50

2026.02.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 9.1万人学习

C 教程
C 教程

共75课时 | 4.6万人学习

C++教程
C++教程

共115课时 | 17万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号