读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的

还是举书上的例子:

1 void PrettyMenu::changeBackground(std::istream& imgSrc)
2 {
3     lock(&mutex);
4     delete bgImage;
5     ++ imageChanges;
6     bgImage = new Image(imgSrc);
7     unlock(&mutex);
8 }

这段代码大致的意思就是改变背景图片,删掉旧的背景图片,记录修改次数,然后创建新的背景图片。考虑到多线程操作,所以这里用了lock和unlock。

但这里会出现问题,因为并不是每次new都会成功的,有可能抛出异常,一旦抛出异常,unlock就没有执行了,这样资源就会一直处于lock状态,而无法继续操作了。另一方面,虽然本次改变背景的操作的失败了,但imageChanges仍然自增了一次,这就不符合程序员设计的初衷了。

有读者就会想,那还不简单,加上try…catch块就行了,像这样:

 1 void PrettyMenu::changeBackground(std::istream& imgSrc)
 2 {
 3     try
 4     {
 5         lock(&mutex);
 6         delete bgImage;
 7         bgImage = new Image(imgSrc);
 8         ++ imageChanges;
 9 unlock(&mutex);
10     }
11     catch (Exception* e)
12     {
13         unlock(&mutex);
14     }
15 }

在catch里面写上unlock函数,另外,调换imageChanges的位置,在new之后再对齐自增。这样做固然可以,但回想一下,我们在条款十三和条款十四做了资源管理类,让类的析构函数自动替我们完成这个操作,不是会更好吗?像这样:

 1 class PrettyMenu
 2 {
 3  4     shared_ptr<Image> bgImage;
 5  6 }
 7 void PrettyMenu::changeBackground(std::istream& imgSrc)
 8 {
 9     Lock m1(&mutex);
10     bgImage.reset(new Image(imgSrc));
11     ++ imageChanges;
12 }

这样,即使抛出了异常,锁资源还有imageChanges都保证是异常发生之前的状态。

现在上升到理论的高度,异常安全性要做到:

1. 不泄漏任何资源

2. 不允许数据败坏

 

带异常安全性的函数会提供三个保证之一:

1. 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或者数据结构会因此被破坏。比如上例中本次更换背景图失败,不会导致相关的数据发生破坏。

2. 强烈保证:在基本承诺的基础上,保证成功就是完全成功,失败也能回到之前的状态,不存在介于成功或失败之间的状态。

3. 不抛出异常:承诺这个代码在任何情况下都不会抛出异常,但这只适用于简单的语句。

 

强烈保证有一种实现方法,那就是copy and swap。原则就是:在修改这个对象之前,先创建它的一个副本,然后对这个副本进行操作,如果操作发生异常,那么异常只发生在这个副本之上,并不会影响对象本身,如果操作没有发生异常,再在最后进行一次swap。

1 void PrettyMenu::changeBackground(std::istream& imgSrc)
2 {
3     Lock m1(&mutex);
4     弄一个临时的tempBgImage
5     对tempBgImage进行操作
6     swap(tempBgImage, bgImage);
7     ++ imageChanges;
8 }

copy-and-swap策略关键在于“修改对象数据的副本,然后在一个不抛异常的函数中将修改后的数据和原件置换”。它确实提供了强异常安全保障,但代价是时间和空间,因为必须为每一个即将被改动的对象造出副本。另外,这种强异常安全保障,也会在下面的情况下遇到麻烦:

1 void someFunc()
2 {
3     f1();
4     f2();
5 }

f1()和f2()都是强异常安全的,但万一f1()没有抛异常,但f2()抛了异常呢?是的,数据会回到f2()执行之前的状态,但程序员可能想要的是数据回复到f1()执行之前。要解决这个问题就需要将f1与f2内容进行融合,确定都没有问题了,才进行一次大的swap,这样的代价都是需要改变函数的结构,破坏了函数的模块性。如果不想这么做,只能放弃这个copy-and-swap方法,将强异常安全保障回退成基本保障。

类似于木桶效应,代码是强异常安全的,还是基本异常安全的,还是没有异常安全,取决于最低层次的那个模块。换言之,哪怕只有一个地方没有考虑到异常安全,整个代码都不是异常安全的。

最后总结一下:

1. 异常安全函数是指即使发生异常也不会泄漏资源或者允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。

2. 强烈保证往往可以通过copy-and-swap实现出来,但“强烈保证”并非对所有函数都可以实现或具备现实意义。

3. 异常安全保证通常最高只等于其所调用之各个函数的异常安全保证中的最弱者。

 

 

posted @ 2013-09-08 20:24  Jerry19880126  阅读(1137)  评论(0编辑  收藏  举报