读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的
还是举书上的例子:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage;
++ imageChanges;
bgImage = new Image(imgSrc);
unlock(&mutex);
}
这段代码大致的意思就是改变背景图片,删掉旧的背景图片,记录修改次数,然后创建新的背景图片。考虑到多线程操作,所以这里用了lock和unlock。
但这里会出现问题,因为并不是每次new都会成功的,有可能抛出异常,一旦抛出异常,unlock就没有执行了,这样资源就会一直处于lock状态,而无法继续操作了。另一方面,虽然本次改变背景的操作的失败了,但imageChanges仍然自增了一次,这就不符合程序员设计的初衷了。
有读者就会想,那还不简单,加上try…catch块就行了,像这样:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
try
{
lock(&mutex);
delete bgImage;
bgImage = new Image(imgSrc);
++ imageChanges;
unlock(&mutex);
}
catch (Exception* e)
{
unlock(&mutex);
}
}
在catch里面写上unlock函数,另外,调换imageChanges的位置,在new之后再对齐自增。这样做固然可以,但回想一下,我们在条款十三和条款十四做了资源管理类,让类的析构函数自动替我们完成这个操作,不是会更好吗?像这样:
class PrettyMenu
{
…
shared_ptr<Image> bgImage;
…
}
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex);
bgImage.reset(new Image(imgSrc));
++ imageChanges;
}
这样,即使抛出了异常,锁资源还有imageChanges都保证是异常发生之前的状态。
现在上升到理论的高度,异常安全性要做到:
1. 不泄漏任何资源
2. 不允许数据败坏
带异常安全性的函数会提供三个保证之一:
1. 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或者数据结构会因此被破坏。比如上例中本次更换背景图失败,不会导致相关的数据发生破坏。
2. 强烈保证:在基本承诺的基础上,保证成功就是完全成功,失败也能回到之前的状态,不存在介于成功或失败之间的状态。
3. 不抛出异常:承诺这个代码在任何情况下都不会抛出异常,但这只适用于简单的语句。
强烈保证有一种实现方法,那就是copy and swap。原则就是:在修改这个对象之前,先创建它的一个副本,然后对这个副本进行操作,如果操作发生异常,那么异常只发生在这个副本之上,并不会影响对象本身,如果操作没有发生异常,再在最后进行一次swap。
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex);
弄一个临时的tempBgImage
对tempBgImage进行操作
swap(tempBgImage, bgImage);
++ imageChanges;
}
copy-and-swap策略关键在于“修改对象数据的副本,然后在一个不抛异常的函数中将修改后的数据和原件置换”。它确实提供了强异常安全保障,但代价是时间和空间,因为必须为每一个即将被改动的对象造出副本。另外,这种强异常安全保障,也会在下面的情况下遇到麻烦:
void someFunc()
{
f1();
f2();
}
f1()和f2()都是强异常安全的,但万一f1()没有抛异常,但f2()抛了异常呢?是的,数据会回到f2()执行之前的状态,但程序员可能想要的是数据回复到f1()执行之前。要解决这个问题就需要将f1与f2内容进行融合,确定都没有问题了,才进行一次大的swap,这样的代价都是需要改变函数的结构,破坏了函数的模块性。如果不想这么做,只能放弃这个copy-and-swap方法,将强异常安全保障回退成基本保障。
类似于木桶效应,代码是强异常安全的,还是基本异常安全的,还是没有异常安全,取决于最低层次的那个模块。换言之,哪怕只有一个地方没有考虑到异常安全,整个代码都不是异常安全的。
最后总结一下:
1. 异常安全函数是指即使发生异常也不会泄漏资源或者允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
2. 强烈保证往往可以通过copy-and-swap实现出来,但“强烈保证”并非对所有函数都可以实现或具备现实意义。
3. 异常安全保证通常最高只等于其所调用之各个函数的异常安全保证中的最弱者。
读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的的更多相关文章
- 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承
private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...
- 读书笔记_Effective_C++_条款四十九:了解new_handler的行为
本章开始讨论内存分配的一些用法,C/C++内存分配采用new和delete.在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常.有的时候,我们希望能 ...
- 读书笔记_Effective_C++_条款二十八:避免返回handlers指向对象内部成分
举个例子: class Student { private: int ID; string name; public: string& GetName() { return name; } } ...
- 读书笔记_Effective_C++_条款二十六:尽可能延后变量定义式的出现时间
这个条款从字面意思还是很好理解的,就是在使用这个变量前才去定义,而不是很早就定义了它,而在很后面的时候才去使用.这个条款只适用于对变量声明位置没有要求的语言,比如C++.对于像C或者一些脚本语言,语法 ...
- 读书笔记_Effective_C++_条款二十五: 考虑写出一个不抛出异常的swap函数
在之前的理论上调用对象的operator=是这样做的 void swap(A& x) { std::swap(a, x.a); } A& operator=(const A& ...
- 读书笔记_Effective_C++_条款二十四: 若所有参数皆需类型转换,请为此采用non-member函数
class A { private: int a; public: A(int x) :a(x){} A operator*(const A& x) { return A(a*x.a); } ...
- 读书笔记_Effective_C++_条款二十二:将成员变量声明为private
1.格式统一 在调用的时候,不会去想有没有(),一律是有get(),或者set()之类的. 2.封装 能直接访问得越少,表明封装性越高, 封装性越高,我们的顾虑就少了, 例如:我们a.data*0.9 ...
- 读书笔记_Effective_C++_条款三十:了解inline的里里外外
学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...
- 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系
这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...
随机推荐
- EasyUi组合条件分页查询
1.引入css与js文件 <link rel="stylesheet" type="text/css" href="themes/default ...
- go标识符、变量、常量
标识符 标识符是用来表示Go中的变量名或者函数名,以字母或_开头.后可跟着字母.数字. _ 关键字 关键字是Go语言预先定义好的,有特殊含义的标识符. 变量 1. 语法:var identifier ...
- LCD常用接口原理【转】
转自:http://blog.csdn.net/wocao1226/article/details/23870149 LCD常用接口原理 点击打开链接 点击打开链接 点击打开链接 点击打开链接 点击打 ...
- 在新版linux上编译老版本的kernel出现kernel/timeconst.h] Error 255
在使用ubuntu16.4编译Linux-2.6.31内核时出现这样的错误 可以修改timeconst.pl的内容后正常编译. 以下是编译错误提示的内容: Can't use 'defined(@ar ...
- MAC和PHY的区别
一块以太网网卡包括OSI(开方系统互联)模型的两个层.物理层和数据链路层.物理层定义了数据传送与接收所需要的电与光信号.线路状态.时钟基准.数据编码和电路等,并向数据链路层设备提供标准接口.数据链路层 ...
- maven工程的建立
/* 我曾经接触过一个Java web项目,在进行部署时,发现这个项目涉及了maven 没有接触过maven项目的我,发现了如果需要导入maven工程,需要先在eclipse里面对maven进行配置, ...
- pandas安装及使用
一. 安装pandas1. Anaconda 安装pandas.Python和SciPy最简单的方式是用Anaconda.Anaconda是关于Python数据分析和科学计算的分发包.2 ...
- 20165301 2017-2018-2 《Java程序设计》第九周学习总结
20165301 2017-2018-2 <Java程序设计>第九周学习总结 教材学习内容总结 第十三章:Java网络编程 URL类 通常包含三部分信息:协议.地址.资源 协议必须是URL ...
- 以太坊go-ethereum项目源码本地环境搭建
如果要深入了解go-ethereum项目的实现与机制,看源代码是必不可少的.今天这篇博客就简单介绍一下如何在本地搭建项目的开发环境. GO语言环境搭建 以win8为例,访问地址https://gola ...
- 抽象工厂模式(Abstract Factory)C#实例
抽象工厂模式(Abstract Factory)C#实例 本文出处http://www.dofactory.com/net/abstract-factory-design-pattern 一.场景描述 ...