Effective C++ 读书笔记 Item29 追求异常安全的代码

探讨异常安全的两个条件,三个保证,以及如何通过智能指针和copyandswap策略实现强烈保证。分析资源管理和数据完整性的关键,以及在现实场景中实现异常安全的挑战。

考虑下面例子,有一个菜单类, changeBg 函数可以改变它的背景,切换背景计数,同时提供线程安全:

class Menu{

    Mutex mutex; //提供多线程互斥访问
    Image *bg; //背景图片
    int changeCount; //切换背景计数

public :
    void changeBg(istream& sr);
};
    void Menu::changeBg(istream& src){
    lock(&mutex);
    delete bg;
    ++changeCount;
    bg = new Image(src);
    unlock(&mutex);
} 

1)异常安全的 2 个条件
异常安全有 2 个条件:
1. 不泄露任何资源:即发生异常时,异常发生之前获得的资源都应该释放,不会因为异常而泄露。在
上面的例子中,如果 new Image 发生异常,那么 unlock 就不会调用,因此锁资源会泄露
2. 不允许数据败坏:上面的例子也不符合,如果 new Image 异常, 背景图片会被删除,计数也会改变。
但是新背景并未设置成功
对于资源泄露, 条款 13 讨论过以对象管理资源。锁资源也可以为 shared_ptr 指定“删除器”,当引用为 0
时,即异常发生,管理所资源的对象被销毁后,删除器会调用 unlock
对于数据败坏:见下文
2)异常安全函数的 3 个保证
1. 基本承诺:抛出异常后,对象仍然处于合法(valid)的状态。但不确定处于哪个状态(对于前面的
例子,如果发生异常, PrettyMenu 可以继续拥有原背景图像,或是令它拥有某个“缺省”的背景图像,
但客户无法确定)
2. 强烈保证:如果抛出了异常, 状态并不会发生发生任何改变。就像没调用这个函数一样
3. 不抛掷保证:这是最强的保证,函数总是能完成它所承诺的事情(作用于内置类型身上的所有操作
都提供 nothrow 保证。这是异常安全代码中一个必不可少的关键基础)
对于前面的 PrettyMenu 对象,可以通过使用智能指针,以及重排 changeBg 的语句顺序来满足“强烈保
证”:

class Menu{
shared_ptr<Image> bg;
...
};
void Menu::changeBg(istream& src){
Lock m1(&mutex); //Lock 以对象管理资源
bg.reset(new Image(src));
++changeCount;
}

注意,上述实现只能为 PrettyMenu 对象提供“强烈保证”,不能提供完美(即全局状态)的“强烈保证”。
比如 Image 构造函数中移动了 istream& src 的读指针然后再抛出异常,那么系统还是处于一个被改变的
状态。 这是一种对整个系统的副作用,类似的副作用还包括数据库操作,因为没有通用的办法可以撤销
数据库操作。 不过这一点可以忽略,我们暂且认为它提供了完美的强烈保证
copy and swap 策略


“copy and swap”设计策略通常能够为对象提供异常安全的“强烈保证”。当我们要改变一个对象时,先把
它复制一份,然后去修改它的副本,改好了再与原对象交换。 关于 swap 的详细讨论可以参见条款 25。
这种策略用在前面的例子中会像这样:

class Menu{
...
    private:
    Mutex mutex;
    std::shared_ptr<MenuImpl> pImpl;
};
Menu::changeBg(std::istream& src){
    using std::swap; // 见 Item 25
    Lock m1(&mutex); // 获得 mutex 的副本数据
    std::shared_ptr<MenuImpl> copy(new MenuImpl(*pImpl));
    copy->bg.reset(new Image(src)); //修改副本数据
    ++copy->changeCount;
    swap(pImpl, copy); //置换数据,释放 mutex
}

copy and swap 策略能够为对象提供异常安全的“强烈保证”。但是一般而言,它并不保证整个函数有“强烈
保证”。也就是说,如果某个函数使用 copy and swap 策略为某个对象提供了异常安全的“强烈保证”。但
是这个函数可能调用其它函数,而这些函数可能改变一些全局状态(如数据库状态),那么”整个函数“就
不是”强烈保证“
函数提供的”异常安全保证“通常最高只等于其所调用的各个函数的”异常安全保证“中的最弱者
除此之外, copy and swap 必须为每一个即将被改动的对象作出一个副本,从而可能造成时间和空间上的
问题
3)最终目标是什么
当”强烈保证“不切实际时(比如前面提到的全局状态改变难以保证,或者效率问题),就必须提供”基本
保证“。现实中你或许会发现,可以为某些函数提供强烈保证,但效率和复杂度带来的成本会使它对许多
人而言摇摇欲坠。只要你曾经付出适当的心力试图提供强烈保证,万一实际不可行,使你退而求其次地
只提供基本保证,任何人都不该因此责难你。对许多函数而言, ”异常安全性的基本保证“是一个绝对同情
达理的选择
总的来说就是,应该为自己的函数努力实现尽可能高级别的异常安全,但是由于种种原因并不是说一定
需要实现最高级别的异常安全,而是应该以此为目标而努力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值