More Effective C++ 条款11:禁止异常流出析构函数之外

More Effective C++ 条款11:禁止异常流出析构函数之外


核心思想

在C++中,析构函数绝对不允许抛出异常。如果异常从析构函数中传播出去,可能会导致程序立即终止或未定义行为,特别是在栈展开过程中处理已有异常时。通过捕获并处理所有析构函数中的异常(通常记录日志或吞掉异常),可以确保程序的安全性和可预测性。16

🚀 1. 问题本质分析

1.1 析构函数中异常导致的严重后果

  • 程序立即终止:如果析构函数在栈展开过程中抛出异常(处理已有异常时),C++运行时调用std::terminate(),程序立即终止16
  • 资源双重泄漏:异常导致析构函数中途退出,可能无法完成所有资源清理工作
  • 未定义行为:异常传播出析构函数可能导致程序进入不可恢复状态

1.2 问题代码示例

// ❌ 危险的析构函数:可能抛出异常
class DangerousDestructor {
public:~DangerousDestructor() {// 可能抛出异常的操作file_.close();       // 可能抛出IOExceptionconnection_.close(); // 可能抛出NetworkExceptiondelete resource_;    // 可能抛出std::bad_alloc(极少数情况)}private:FileHandler file_;NetworkConnection connection_;Resource* resource_;
};// 使用示例
void demonstrateProblem() {try {DangerousDestructor obj;// 使用obj...} catch (const std::exception& e) {// 如果obj的析构函数抛出异常,程序可能终止!}
}

📦 2. 问题深度解析

2.1 为什么析构函数抛出异常如此危险

  • 栈展开冲突:C++不允许同时存在两个活跃异常,这会直接导致程序终止1
  • 资源清理不完整:异常导致析构函数提前退出,后续清理代码不会执行
  • 难以诊断:此类问题通常在异常情况下才会暴露,难以测试和重现

2.2 错误处理模式分析

// ❌ 不完全的异常处理(仍然危险)
class IncompleteHandling {
public:~IncompleteHandling() {try {file_.close();       // 可能抛出异常connection_.close(); // 可能抛出异常} catch (...) {// 只是捕获了异常,但没有妥善处理// 异常仍然可能传播出去(取决于编译器)}}private:FileHandler file_;NetworkConnection connection_;
};// ❌ 错误:重新抛出异常
class RethrowInDestructor {
public:~RethrowInDestructor() {try {cleanupResources();} catch (...) {// 做一些处理...throw;  // 致命错误:在析构函数中重新抛出异常}}
};

⚖️ 3. 解决方案与最佳实践

3.1 基本解决方案:捕获并处理所有异常

// ✅ 安全的析构函数:捕获所有异常
class SafeDestructor {
public:~SafeDestructor() noexcept {  // C++11: 使用noexcept确保不抛出异常try {// 可能抛出异常的操作file_.close();connection_.close();delete resource_;} catch (const std::exception& e) {// 记录日志:析构函数中的异常应该被记录logError("Exception in destructor: ", e.what());// 不重新抛出异常:这是关键!}catch (...) {// 捕获所有其他异常logError("Unknown exception in destructor");}}private:void logError(const std::string& message, const std::string& detail = "") {// 实现日志记录(不应抛出异常)std::cerr << message << detail << std::endl;// 或者使用无异常保证的日志库}FileHandler file_;NetworkConnection connection_;Resource* resource_;
};

3.2 使用RAII包装可能异常的操作

// ✅ 创建不会抛出异常的RAII包装器
class SafeFileHandler {
public:SafeFileHandler(const std::string& filename) : file_(filename) {}~SafeFileHandler() noexcept {try {if (file_.is_open()) {file_.close();}} catch (...) {// 记录错误,但不允许异常传播std::cerr << "Failed to close file" << std::endl;}}// 提供安全的操作接口void write(const std::string& data) {file_ << data;}private:std::ofstream file_;
};// 使用安全的RAII包装器
class SafeResource {
public:~SafeResource() noexcept {  // 现在安全了:成员析构不会抛出异常// 所有可能异常的操作都被包装在SafeFileHandler等类中}private:SafeFileHandler file_;SafeNetworkConnection connection_;std::unique_ptr<Resource> resource_;  // 使用智能指针
};

3.3 提供显式清理方法

// ✅ 两段式清理:提供显式清理方法
class ExplicitCleanup {
public:// 显式清理方法:可以抛出异常void close() {file_.close();       // 可能抛出异常connection_.close(); // 可能抛出异常isClosed_ = true;}// 析构函数:检查是否已清理,如果没有则安全清理~ExplicitCleanup() noexcept {if (!isClosed_) {try {// 安全地清理资源(不抛出异常)try { file_.close(); } catch (...) {}try { connection_.close(); } catch (...) {}} catch (...) {// 理论上不应该发生,但为了安全起见std::cerr << "Unexpected exception during emergency cleanup" << std::endl;}}}private:FileHandler file_;NetworkConnection connection_;bool isClosed_ = false;
};// 使用示例
void useExplicitCleanup() {ExplicitCleanup obj;try {// 使用obj...obj.close();  // 显式清理:可以处理异常} catch (const std::exception& e) {// 处理清理异常std::cerr << "Cleanup failed: " << e.what() << std::endl;}// 析构函数不会抛出异常
}

3.4 现代C++增强

// 使用noexcept规范(C++11及以上)
class ModernSafeDestructor {
public:~ModernSafeDestructor() noexcept {  // 明确声明不抛出异常try {// 可能抛出异常的操作cleanup();} catch (...) {handleException(std::current_exception());}}private:void cleanup() {// 清理操作可能抛出异常}void handleException(std::exception_ptr eptr) noexcept {try {std::rethrow_exception(eptr);} catch (const std::exception& e) {// 使用无异常保证的日志记录logNoExcept(e.what());} catch (...) {logNoExcept("Unknown exception in destructor");}}void logNoExcept(const char* message) noexcept {// 保证不抛出异常的日志实现// 例如:写入标准错误或预分配的内存缓冲区std::cerr << message << std::endl;}
};// 使用scope_guard模式(C++11/14/17)
template<typename Func>
class ScopeGuard {
public:explicit ScopeGuard(Func cleanup) : cleanup_(std::move(cleanup)), active_(true) {}~ScopeGuard() noexcept {if (active_) {try {cleanup_();} catch (...) {// 不允许异常传播logException(std::current_exception());}}}void dismiss() noexcept { active_ = false; }// 禁止拷贝和移动ScopeGuard(const ScopeGuard&) = delete;ScopeGuard& operator=(const ScopeGuard&) = delete;private:Func cleanup_;bool active_;static void logException(std::exception_ptr) noexcept {// 异常记录实现}
};// 使用示例
void useScopeGuard() {FileHandler file("test.txt");auto guard = ScopeGuard([&file] { file.close();  // 可能抛出异常});// 使用文件...// 如果正常完成,解除guard的清理责任guard.dismiss();file.close();  // 显式关闭:可以处理异常
}

💡 关键实践原则

  1. 始终将析构函数声明为noexcept
    C++11及以上版本应明确声明析构函数为noexcept:

    class ModernClass {
    public:~ModernClass() noexcept {  // 正确:明确禁止异常传播// 析构函数实现}
    };
    
  2. 彻底处理所有可能的异常
    在析构函数中捕获所有异常并适当处理:

    ~MyClass() noexcept {try {// 可能抛出异常的操作} catch (const std::exception& e) {// 记录异常信息(使用无异常保证的日志)logSafe(e.what());}catch (...) {// 处理未知异常logSafe("Unknown exception in destructor");}
    }
    
  3. 使用RAII包装可能异常的操作
    创建专门负责资源清理的RAII类:

    template<typename T>
    class SafeCleanup {
    public:SafeCleanup(T& resource) : resource_(resource) {}~SafeCleanup() noexcept {try {cleanup(resource_);} catch (...) {// 安全地处理异常}}private:T& resource_;// 特化或重载cleanup函数用于不同类型static void cleanup(FileHandler& file) { /* 安全实现 */ }static void cleanup(NetworkConnection& conn) { /* 安全实现 */ }
    };
    
  4. 提供显式清理接口
    对于复杂资源,提供可抛出异常的显式清理方法:

    class ComplexResource {
    public:// 显式清理:可抛出异常void close() {cleanupPhase1();cleanupPhase2();cleanupPhase3();isClosed_ = true;}~ComplexResource() noexcept {if (!isClosed_) {emergencyCleanup();  // 不抛出异常的安全清理}}private:bool isClosed_ = false;void emergencyCleanup() noexcept {// 最简单的安全清理实现// 不保证完全清理,只保证不抛出异常}
    };
    
  5. 代码审查要点

    • 检查所有析构函数是否声明为noexcept(C++11及以上)
    • 确认析构函数中没有可能传播出去的异常
    • 验证所有资源清理操作都有适当的异常处理
    • 确保日志记录操作本身不会抛出异常
    • 检查复杂类是否提供了显式清理接口

总结

析构函数中禁止异常传播是C++异常安全编程的基本原则。违反这一原则会导致程序终止和未定义行为。通过将析构函数标记为noexcept、彻底捕获和处理所有异常、使用RAII包装危险操作、以及提供显式清理接口,可以确保析构函数的安全性和可靠性。在资源清理方面,应优先考虑使用已经正确处理异常的RAII组件,而不是在每个析构函数中重复实现异常处理逻辑。

额外建议

  • 使用静态分析工具检测可能抛出异常的析构函数
  • 在单元测试中模拟资源清理失败的情况
  • 文档中明确记录哪些方法可能抛出异常,哪些保证不抛出异常
  • 对于第三方库的资源,创建适配器包装器以确保异常安全

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/94738.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/94738.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

商超高峰客流统计误差↓75%!陌讯多模态融合算法在智慧零售的实战解析

原创声明&#xff1a;本文为原创技术解析&#xff0c;核心技术参数、架构设计及实战数据引用自 “陌讯技术白皮书”&#xff0c;技术方案与落地案例结合aishop.mosisson.com智慧零售数据联动场景展开&#xff0c;禁止未经授权的转载与商用。 一、行业痛点&#xff1a;智慧零售…

PyTorch实战(2)——使用PyTorch构建神经网络

PyTorch实战&#xff08;2&#xff09;——使用PyTorch构建神经网络0. 前言1. PyTorch 构建神经网络初体验1.1 使用 PyTorch 构建神经网络1.2 神经网络数据加载1.3 模型测试1.4 获取中间层的值2. 使用 Sequential 类构建神经网络3. PyTorch 模型的保存和加载3.1 模型保存所需组…

关于git的安装(windows)

1.git的介绍 Git 是一个分布式版本控制系统&#xff0c;由 Linus Torvalds 在 2005 年为 Linux 内核开发而创建。它能够高效地处理从小型到超大型项目的版本管理&#xff0c;具有以下特点&#xff1a; 分布式架构&#xff1a;每个开发者本地都有完整的仓库副本高效性能&#…

Java后端开发?接口封装器!

开发接口确实是Java后端开发中最核心、最可见的产出工作。“对入参校验、处理业务逻辑、返回格式处理”——精准地描述了一个API接口的核心处理流程。 但这只是冰山之上最直观的部分。一个专业、稳健、可扩展的后端系统&#xff0c;其复杂性和价值绝大部分隐藏在冰山之下。结合…

【沉浸式解决问题】NVIDIA 显示设置不可用。 您当前未使用连接到NVIDIA GPU 的显示器。

目录一、问题描述二、环境版本三、原因分析四、解决方案一、问题描述 在看一篇cuda安装的教程时&#xff0c;第一步是打开NVIDIA 控制面板&#xff0c;但是我打不开&#xff1a; NVIDIA 显示设置不可用。 您当前未使用连接到NVIDIA GPU 的显示器。 二、环境版本 设备&#xf…

牛客周赛 Round 106(小苯的方格覆盖/小苯的数字折叠/ 小苯的波浪加密器/小苯的数字变换/小苯的洞数组构造/ 小苯的数组计数)

A 小苯的方格覆盖思路&#xff1a;怎么摆第三行都是横放的2*1&#xff1b;故若n为奇数&#xff0c;总格子数3n为奇数&#xff0c;无法被2整除&#xff0c;直接排除。#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<iostream> #include<bits/stdc…

高并发内存池(16)-三层缓存的回收过程

高并发内存池&#xff08;16&#xff09;-三层缓存的回收过程 内存池的回收过程是内存管理系统的关键环节&#xff0c;它通过分层协作和智能合并机制&#xff0c;确保内存高效重复利用。以下是完整的回收流程解析&#xff1a;一、回收触发场景 ThreadCache回收&#xff1a;线程…

深入解析MyBatis Mapper接口工作原理

在Java持久层框架中&#xff0c;MyBatis以其灵活性和易用性赢得了广大开发者的青睐。作为MyBatis的核心概念之一&#xff0c;Mapper接口机制极大地简化了数据库操作代码的编写。本文将深入剖析MyBatis Mapper接口的工作原理&#xff0c;从基础概念到底层实现&#xff0c;帮助开…

疯狂星期四文案网第49天运营日记

网站运营第49天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 今日访问量 常州苏州那些ip锲而不舍的扫了很多php的页面 今日搜索引擎收录情况 k页面比较严重了&#xff0c;哎。 我感觉不该做其他类型文案的 网…

从GPT-5发布来分析LLM大模型幻觉收敛(一)

GPT-5 号称在任何领域都有博士级别能力。在医疗健康领域&#xff0c;能够对专业的癌症诊断报告做通俗易懂的解读。对复杂的放射治疗方案决策&#xff0c;也能提供详细的分析报告&#xff0c;帮助病人权衡利弊。一位癌症患者的家属在发布会上表示&#xff0c;“ 真正鼓舞人心的是…

大模型安全概述、LlamaFirewall

资料搜集整理自网络。 概述 大模型爆火之后&#xff0c;衍生出大模型安全这一个比较新的领域。和之前的文章一样&#xff0c;本文有不少新颖的名词、概念、理论。 信通院、清华大学等多个单位联合发布的《大模型安全实践&#xff08;2024&#xff09;》&#xff0c;提出LLM安…

【目标检测】论文阅读3

Lightweight tomato ripeness detection algorithm based on the improved RT-DETR 论文地址 摘要 番茄具有很高的营养价值&#xff0c;需要对成熟果实进行准确的成熟度鉴定和选择性采收&#xff0c;以显著提高番茄收获管理的效率和经济效益。以往对番茄智能收获的研究往往只以…

Python音频分析与线性回归:探索声音中的数学之美

摘要&#xff1a;通过Python实现WAV音频信号处理与线性回归建模&#xff0c;揭示双声道音频的数学关联性&#xff0c;为声音特征分析提供新视角。1. 音频数据处理流程 1.1 WAV文件读取与预处理 使用scipy.io.wavfile读取音频文件&#xff0c;获取采样率与时域信号数据&#xff…

Linux shell脚本数值计算与条件执行

变量的数值计算实践 1 算术运算符 如果要执行算术运算&#xff0c;就会离不开各种运算符号&#xff0c;和其他编程语言类似&#xff0c;Shell 也有很多算术运算符。 下面就给大家介绍一下常见的 Shell 算术运算符&#xff1a; 、-&#xff0c;一元正号和负号。、-&#xff0c;加…

C#实战:基于iTextSharp实现PDF加密小工具

目录 1、技术框架 2、代码实战 2.1 创建窗体 2.2 后台代码逻辑 2.3 PDF加密用户类型 2.4 PDF加密权限列表 3、运行效果 4、总结 大家日常办公中有时候为了文档资料的安全需要对文档进行加密,尤其是针对PDF文档这个场景还是非常广泛的。今天给大家分享使用C#来实现PDF…

基于Labview的旋转机械AI智能诊断系统

1.摘要本文基于 CWRU 公开轴承数据集提出了一套“AI 轻量级模型 LabVIEW 智能诊断系统”。首先&#xff0c;LabVIEW 端构建了可视化、可交互的智能诊断平台。系统能够加载本地振动信号数据&#xff0c;调用训练好的深度学习模型进行故障识别与状态判断。界面集成信号时域监测、…

Qt从qmake迁移到cmake的记录

文章目录1.UI程序[开启/关闭]控制台2.增加宏定义3.在主项目中引入子项目4.使用C语言文件1.UI程序[开启/关闭]控制台 qmake&#xff1a; CONFIG console DEFINES QT_MESSAGELOGCONTEXTcmake&#xff1a; set(CMAKE_WIN32_EXECUTABLE OFF) # ON为关闭控制台 OFF为开启控制台2…

LangChain4J-(3)-模型参数配置

LangChain4j 提供了灵活的模型参数配置方式&#xff0c;允许你根据不同的 AI 模型&#xff08;如 OpenAI、GPT-4、Anthropic 等&#xff09;设置各种参数来控制生成结果。后面手撸代码继续在之前章节的代码上拓展一、日志配置&#xff08;Logging&#xff09;在 LangChain4j 中…

LangGraph - API多种访问方式

本文介绍了Langgraph服务的四种调用方式&#xff1a;1. 通过LangGraph Studio UI界面手动测试&#xff1b;2. 使用Python SDK进行同步/异步调用&#xff1b;3. 通过REST API测试&#xff1b;4. 使用JavaScript SDK接入。Langgraph 服务端代码 graph.pyfrom langchain_openai im…

HEI-612 HART/EtherNet/IPModbus TCP 网关:打通工业通信壁垒

在工业自动化领域&#xff0c;HART 协议设备的广泛应用与以太网网络的高效管理常面临 “协议孤岛” 难题 —— 老旧 HART 传感器、变送器难以接入 EtherNet/IP 或 Modbus TCP 系统&#xff0c;数据双向交互卡顿、调试复杂、兼容性差等问题&#xff0c;严重制约生产效率提升。上…