【读书笔记】Effective C++ 条款8:别让异常逃离析构函数

目录

一、数组析构时的异常问题

二、用对象管理资源时的析构异常

三、处理析构异常的三种策略

[1. 直接结束程序(abort)](#1. 直接结束程序(abort))

[2. 吞下异常(记录日志)](#2. 吞下异常(记录日志))

[3. 让客户自己解决(推荐)](#3. 让客户自己解决(推荐))

四、关于"甩锅"的讨论


一、数组析构时的异常问题

假设 vector<Widget> 中的 Widget 对象在析构时抛出异常。

C++ 规定:同时存在两个未处理的异常时,程序会终止。

由于第一个 Widget 析构抛出异常时,第二个、第三个 Widget 的析构还在继续,此时就有两个异常同时存在,因此程序会被终止。

此外,析构函数抛出异常可能导致内存释放不彻底,造成资源泄漏

结论:析构函数最好不要抛出异常。

C++ 新标准 :规定 vector 等容器在析构时不能抛出异常,否则程序直接终止。

解决方案 :使用 noexcept 关键字,声明该函数不会抛出异常。如果实际抛出了异常,会直接调用 std::terminate() 结束程序。

二、用对象管理资源时的析构异常

有时会用一个额外的 manageA 对象来管理对象 A,当 manageA 生命周期结束时,其析构函数会调用 Aclose 接口。

cpp 复制代码
class A {
public:
    static A create() {
        return A();
    }
    void close() {}  // 没人规定 close 不会抛异常
};

class manageA {
public:
    manageA() {
        _a = A::create();
    }
    ~manageA() {
        _a.close();   // 如果 close 抛异常,析构函数就会抛出异常
    }
private:
    A _a;
};

如果 close 接口可能抛出异常,那么 manageA 的析构函数就可能抛出异常,这很麻烦。

三、处理析构异常的三种策略

1. 直接结束程序(abort)

最粗暴的方法:在析构函数中捕获异常,然后调用 abort() 结束程序。

cpp 复制代码
class manageA {
public:
    manageA() {
        _a = A::create();
    }
    ~manageA() {
        try {
            _a.close();
        } catch (...) {
            abort();  // 捕获任何异常后立即终止程序
        }
    }
private:
    A _a;
};

合理性:阻止异常从析构函数中传播出去,符合 C++ 的规范要求。

2. 吞下异常(记录日志)

捕获异常但不传播,仅记录日志。

cpp 复制代码
~manageA() {
    try {
        _a.close();
    } catch (...) {
        LOG(LogLevel::DEBUG) << "error";  // 记录错误日志
    }
}

要求:采用这种方案,必须确保吞掉异常后,程序仍然能够正常运行。

有时候吞下异常的方式比 abort 结束程序更好。

3. 让客户自己解决(推荐)

最好的方式是将问题甩给程序员,而不是预设是吞掉还是结束。

cpp 复制代码
class manageA {
public:
    manageA() {
        _a = A::create();
    }
    
    // 提供 close 接口,让用户主动调用
    void close() {
        _a.close();
    }
    
    ~manageA() {
        try {
            _a.close();
        } catch (...) {
            // 什么都不做,或者记录日志
            // 但此时异常已经由用户主动调用 close 时处理过了
        }
    }
private:
    A _a;
};

设计思路

  • 提供一个 close 接口,让用户有机会主动调用

  • 如果用户调用了 close 接口,就有充足的时间去处理可能抛出的异常

  • 如果用户没有调用 close 接口,那么析构时出现异常,责任在用户------因为他本来有机会去调用 close 接口

四、关于"甩锅"的讨论

作者承认:这个行为看起来像是在甩锅给用户,可能有人认为这违反了"让接口容易正确使用"的准则。

反驳

  • 提供一个 close 接口,反而是给了用户处理异常的机会

  • 用户可以选择调用 close 来主动处理异常,也可以选择不调用

  • 不调用的后果由用户自己承担

这是一种责任分离的设计:资源管理类负责自动释放资源,但异常处理的责任交给用户主动调用接口来完成。

相关推荐
叶总没有会1 分钟前
3.2 构建AI智能体项目扩展知识
java·数据库·人工智能·spring·ai
Lakers-243 分钟前
1987年6月10日晚上23-24点出生性格、运势和命运
java
唐青枫5 分钟前
Java Jetty 实战详解:从嵌入式 HTTP 服务到 Spring Boot 容器替换
java
Scott9999HH8 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔8 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖10 小时前
JDK 26 新特性详解
java·开发语言
马优晨11 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区13 小时前
怎么把C语言学到精通?
c语言·开发语言