C++ 异常安全(五):保障异常安全

注意! “异常安全”是 Modern C++ 的重要线索,可以将现代 C++ 的设计理念串联起来,本文对其作一个简要介绍,本文也是 C++ 异常安全系列最核心的一篇~

异常安全(exception safety)即保证不会因抛出异常而造成错误或资源泄漏等问题。

异常安全的级别:

  1. 基本保证(basic guarantee):抛出异常后,保证无资源泄漏,所有对象均处于有效状态
  2. 强保证(strong guarantee):如果某个过程抛出异常,保证程序的状态与执行该过程之前相同
  3. 不抛异常保证(nothrow guarantee):保证操作不会失败,也不会抛出任何异常

程序应至少提供基本保证,关键过程应实现强保证,与异常机制相关的过程,如析构函数、资源回收函数等,则应实现不抛异常保证。

示例:

void foo(vector<T>& v) {
    v.push_back(T());      // Strong guarantee
}

例中标准容器 vector 的 push_back 函数提供强保证,如果抛出异常,容器中的元素以及容器的容量均不会发生任何变化。

又如:

void bar() {
    lock();
    procedure_may_throw();   // Non-compliant
    unlock();
}

设 lock 是某种获取资源的操作,unlock 是释放资源的操作,procedure_may_throw 是可能抛出异常的过程,那么 bar 函数就不是异常安全的,一旦抛出异常就会导致死锁或泄漏等问题。

应保证资源从分配到回收的过程不被异常中断,如捕获异常,在重新抛出异常前释放资源:

void bar() {
    lock();
    try {
        procedure_may_throw();   // Compliant, but verbose
    } catch (...) {
        unlock();
        throw;
    }
    unlock();
}

但分散的回收操作容易出错,显式的 try-cath 语句也不利于维护。

将资源托管于类对象,使资源的生命周期协同于对象的生命周期,自动完成分配和回收是更好的方式:

void bar() {
    LockOwner object;        // Safe and brief
    procedure_may_throw();   // Compliant
}

使 lock 和 unlock 分别由 object 的构造和析构函数完成,即使 procedure_may_throw 抛出异常,相关资源也会被自动回收,资源的对象化管理方法可参见 ID_ownerlessResource 的进一步讨论。

异常安全的另一个重要方面是抛出异常时应保证对象的状态是正确的,事务或算法在处理对象时可能要分多个步骤处理对象的多个成员,要注意中途抛出异常会造成数据不一致等问题。

class X {
    T a, b;

public:
    void job() {
        proc(a);   // May throw exceptions
        proc(b);   // Unsafe, ‘a’ and ‘b’ may be inconsistent
    }
};

设 a 和 b 是两个密切相关的成员,如账号和金额等,job 是处理事务的函数,如果在中途抛出异常就会使对象处于错误的状态,解决方法可以考虑“复制 - 交换”模式,如:

class X {
    T a, b;
public:
    void job() {
        X copy(*this);
        proc(copy.a);
        proc(copy.b);
        swap(copy);     // Copy-and-swap idiom
    }
    void swap(X&) noexcept;   // Nothrow guarantee
};

先处理对象的副本,处理成功后交换副本与对象的数据,交换过程需要保证不抛出异常,这样从对象副本的生成到事务处理完毕的过程中即使抛出异常也不影响对象的状态,实现了强异常安全保证。

更进一步地,可参见如下详细介绍:

  1. 保证异常安全
  2. 使资源接受对象化管理
  3. 析构函数不可抛出异常
  4. 内存回收函数不可抛出异常
  5. 对象交换过程不可抛出异常
  6. 移动构造函数和移动赋值运算符不可抛出异常
  7. 异常类的拷贝、移动构造函数和析构函数均应是可访问的
  8. 使用 noexcept 关键字标注不抛出异常的函数
  9. 异常类的拷贝构造函数不可抛出异常
  10. 禁用动态异常说明

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值