条款29:为“异常安全”而努力是值得的

 假设我们有一个类来表示带有背景图像的GUI菜单。这个类被设计为支持多线程环境,所以它有一个互斥量来控制并发:

class PrettyMenu {
public:
    ...
    void changeBackground(std::istream& imgSrc); // 改变背景图像
    ... 
private:
    Mutex mutex; 	// 这个对象的互斥量 
    Image* bgImage; 	// 当前背景图像
    int imageChanges; // 图像被改变的次数
};

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    lock(&mutex); 	// 取得互斥量(见条款14)
    delete bgImage; 	// 去除旧的背景
    ++imageChanges; 	// 更新图像变化计数
    bgImage = new Image(imgSrc); // 安装新的背景
    unlock(&mutex); 	// 释放互斥量
}

当异常被抛出时,异常安全的函数应该具备:

  • 不泄漏资源。
  • 不允许数据结构成为损坏的状态

 从异常安全性的角度来看,这个函数很糟糕。异常安全性有两个要求,这里都不满足。
 解决资源泄漏问题很容易,在条款13中我们已经解释了如何使用对象来管理资源,条款14引入了Lock类,以确保互斥量及时释放:

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    Lock ml(&mutex); // 取得互斥量,并确保释放(见条款14)
    delete bgImage;
    ++imageChanges;
    bgImage = new Image(imgSrc);
}

具备异常安全性的函数,提供以下三种保证之一:
1、基本承诺:如果抛出异常,程序中的所有内容都保持有效状态。
2、强烈保证:如果抛出异常,程序的状态不会改变(要么全部成功,要么就像没发生过)。
3、不抛出异常(nothrow)保证:它们总是按照承诺的方式执行。内置类型(例如整型、指针等)上的所有操作都是nothrow(即提供nothrow承诺)。这是构建异常安全代码的关键组成。
下面的代码改变了bgImage的类型,使用智能指针不但可以防止资源泄漏,还提供了异常安全性;我们还改变了++imageChanges;语句的位置:

class PrettyMenu {
    ...
    std::shared_ptr<Image> bgImage;
    ...
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    Lock ml(&mutex);
    bgImage.reset(new Image(imgSrc)); // 用“new Image”表达式的结果
					  // 替换bgImage的内部指针
    ++imageChanges;
}

 shared_ptr::reset函数只有在其参数“new Image(imgSrc)”成功创建时才会被调用。delete只在reset调用中使用,所以如果从未进入该函数,那么就永远不会使用delete。
 美中不足的是参数imgSrc。如果Image构造函数抛出异常,输入流的read标记可能被移动了,而这种移动对程序的其他部分来说是可见的状态变化。在changeBackground解决这个问题之前,它只能提供异常安全性的基本承诺。
另一个常用于提供强烈保证的方法是我们所提到过的 copy and swap:

struct PMImpl { // PMImpl = "PrettyMenu Impl."; 
    std::shared_ptr<Image> bgImage; 
    int imageChanges; 
};
class PrettyMenu {
    ...
private:
    Mutex mutex;
    std::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
    using std::swap; // 见条款25
    Lock ml(&mutex); // 获取互斥量
    std::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));// 复制obj. data
    pNew->bgImage.reset(new Image(imgSrc)); // 改变副本
    ++pNew->imageChanges;
    swap(pImpl, pNew); // 交换数据
} // 释放互斥量
  • 异常安全的函数不会泄漏资源,也不允许数据结构损坏,即使在抛出异常时也是如此。这些函数提供了基本的、强的或无抛出的保证。
  • 强保证通常可以通过复制-交换实现,但并不是对所有函数都适用。
  • 函数提供的保证通常不会超过它调用的函数的最弱保证。
  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值