当前位置: 首页 > news >正文

析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?

在我们不做过多处理的情况下,析构函数是绝对不能抛出异常的,这会直接导致我们的整个程序被强制终止

现象

#include <iostream> #include <stdexcept> using namespace std; ​ class Bad { public: ~Bad() { // C++11起默认 noexcept cout << "~Bad: 准备抛异常\n"; throw runtime_error("析构里抛的异常"); } }; ​ int main() { try { Bad a; // a 离开作用域,析构 } catch (const exception& e) { cout << "捕获到: " << e.what() << "\n"; } cout << "main 正常结束\n"; // 这行不会执行 }

C++关于异常处理的两点规则:

  1. 在栈展开过程中,如果析构函数抛出异常且当前已有未捕获异常,会直接调用std::terminate

  2. 析构函数默认隐式noexcept,只要它内部抛出异常,无论当前有没有其他异常,都会直接调用std::terminate

class Bad2 { public: ~Bad2() noexcept(false) { // 显式声明可能抛 throw runtime_error("析构异常"); } };

我们可以通过显式指定noexcept(false)的方式去告诉编译器,我们这个析构函数可以抛出异常,这样就会被成功地捕获到

class Leaky { FILE* f; public: Leaky() { fopen_s(&f, "a.cpp", "w"); } ~Leaky() noexcept(false) { cout << "~Leaky: 准备抛异常\n"; // ① 清理 throw runtime_error("boom"); // ② 抛出,析构后面的逻辑全跳过 cout << "这行永远不执行\n"; // 演示用 fclose(f); } };

不过我们在实际的编程中也不建议去显式的指定析构函数可以抛异常,也不建议去让它抛异常,因为这很可能导致一些资源的泄露。比如上面这个代码在抛出异常之后,异常后面的代码是永远不会被执行的,如果后面是会去释放一些大量资源,这就会导致内存泄漏

真正致命的——栈展开时再抛

class Bad2 { public: ~Bad2() noexcept(false) { cout << "~Bad2 析构,再抛一个\n"; throw runtime_error("析构异常2"); } }; ​ void foo() { Bad2 a; throw runtime_error("原始异常"); // 触发栈展开 → a析构 → 再抛 } ​ int main() { try { foo(); } catch (const exception& e) { cout << "抓到: " << e.what() << "\n"; } }

正确做法

class Good { public: ~Good() noexcept { try { cleanup(); // 可能失败的操作 } catch (...) { // 吞掉,记日志,别让它跑出去 cerr << "析构清理失败,已忽略\n"; } } void cleanup(); // 用户可以显式调用并处理错误 ​ // 进阶:提供 close(),让用户主动调用、主动 catch void close() { /* 真正可能失败的清理 */ } };

析构里try-catch(...)吞掉所有异常;如果清理真的可能失败且用户该知道,就提供close()让用户显式调用。

http://www.jsqmd.com/news/1144246/

相关文章:

  • 关于高维数据下的最近邻搜索算法性能分析的技术7
  • 如何快速解锁加密音乐:完整使用指南解决格式限制问题
  • macOS SSH 密钥配置全流程:从生成到 GitHub 添加的 5 个关键步骤与排错
  • 37、<简单>元素插入有序数组
  • fdisk 与 parted 实战对比:MBR vs GPT 分区表,2TB 硬盘分区的 3 个关键决策点
  • AI 模型可解释性:SHAP 值不是万能的,特征相关性要先看
  • [linux] nohup和pm2的区别 进程保活
  • 群晖DSM 7.2.2视频管理终极方案:一键脚本恢复Video Station完整功能
  • Docker vs 虚拟机:5个维度实测对比资源消耗与启动速度
  • Ventoy vs Rufus:2种工具在移动硬盘部署Ubuntu 22.04的引导与兼容性实测
  • IntelliJ IDEA 2025.1深度解析:原生二进制+Kotlin语义级支持+本地AI引擎
  • WeChatMsg:微信聊天记录的终极备份神器,让珍贵对话永不丢失
  • 5分钟从零到一:AI视频生成如何让普通人也能制作专业短视频?
  • Win7 虚拟机无网络安装 VMware Tools:3种文件传输方案对比
  • LangChain 6大核心调用方法:invoke/stream/batch同步异步全解析,新手也能轻松学会
  • GNOME Tweaks 45 插件管理:3种安装方式对比与5个必装扩展推荐
  • vsftpd 3.0.5 RPM 安装依赖冲突解决:3种方案处理 Failed dependencies 报错
  • A3910与PIC18F87K22在嵌入式电机控制中的高效应用
  • Linux find 与 xargs/exec 性能对比:处理 10 万文件的 3 种方案实测
  • TMC7300与STM32F429ZI驱动有刷直流电机方案解析
  • SQL Server 2022 备份恢复实战:3种恢复模式与5天备份周期策略配置
  • Ubuntu 22.04 LTS 移动硬盘安装:3步解决EFI引导跨电脑即插即用难题
  • Windows系统文件CloThumbnailProvider.dll丢失找不到问题解决
  • 如何快速部署VC++运行库:VisualCppRedist AIO终极解决方案
  • 边缘端YOLO轻量化部署:模型裁剪+INT8量化完整实操指南
  • BurpBounty自定义扫描规则:从原理到实战,提升Burp Suite漏洞检测精准度
  • IAR EWARM历史版本安装实战:嵌入式产线环境复现指南
  • 深入理解Web
  • 3种主流数据库(MySQL 8.0 / PostgreSQL 15 / SQL Server 2022)考勤系统性能对比
  • PotPlayer终极字幕翻译指南:三步实现免费多语言实时翻译