条款28 避免返回handles指向对象内部成分 条款29 为“异常安全”而努力是值得的
假设有个class用来表现夹带背景图案的GUI菜单,这个class希望用于多线程环境,所以它有个互斥器(mutex)作为并发控制:
class PrettyMenu { public: //改变背景图像 void changeBackground(std::istream& imgSrc); private: Mutex mutex; //互斥器 Image* bgImage; //目前的图像 int imageChanges; //背景图像被改变的次数 } //一种实现 void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); delete bgImage; ++ imageChanges; bgImage = new Image(imgSrc); unlock(&mutex); }但这里会出现问题,因为并不是每次new都会成功的,有可能抛出异常,一旦抛出异常,unlock就没有执行了,这样资源就会一直处于lock状态,而无法继续操作了。另一方面,虽然本次改变背景的操作的失败了,但imageChanges仍然自增了一次,这就不对了。
具有异常安全的函数会:
不泄露任何资源不允许数据被破坏其中保证mutex的方法就是使用,条款14的互斥器
class PrettyMenu { … shared_ptr<Image> bgImage; … } void PrettyMenu::changeBackground(std::istream& imgSrc) { Lock m1(&mutex); bgImage.reset(new Image(imgSrc)); ++ imageChanges; }以对象管理资源是常识。即使抛出了异常,锁资源还有imageChanges都保证是异常发生之前的状态。
基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或者数据结构会因此被破坏。比如上例中本次更换背景图失败,不会导致相关的数据发生破坏。
强烈保证:在基本承诺的基础上,保证成功就是完全成功,失败也能回到之前的状态,不存在介于成功或失败之间的状态。
不抛出异常:承诺这个代码在任何情况下都不会抛出异常,但这只适用于简单的语句。
struct PMImpl { std::tr1::shared_ptr<Image> bgImage; int imageChanges; }; class PrettyMenu { ... private: Mutex mutex; std::tr1::shared_ptr<PMImpl> pImpl; }; void PrettyMenu::changeBackground(std::istream& imgSrc) { using std::swap; Lock m1(&mutex); //一份副本 std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); //修改副本 pNew->bgImage.reset(new Image(imgSrc)); bgImage.reset(new Image(imgSrc)); ++pNew->imageChanges; //将这个修改好的副本 替换成自己的成员 swap(pImpl, pNew); }opy-and-swap策略关键在于“修改对象数据的副本,然后在一个不抛异常的函数中将修改后的数据和原件置换”。它确实提供了强异常安全保障,但代价是时间和空间,因为必须为每一个即将被改动的对象造出副本。另外,这种强异常安全保障,也会在下面的情况下遇到麻烦:
void someFunc() { f1(); f2(); }1()和f2()都是强异常安全的,但万一f1()没有抛异常,但f2()抛了异常呢?是的,数据会回到f2()执行之前的状态,但程序员可能想要的是数据回复到f1()执行之前。要解决这个问题就需要将f1与f2内容进行融合,确定都没有问题了,才进行一次大的swap,这样的代价都是需要改变函数的结构,破坏了函数的模块性。如果不想这么做,只能放弃这个copy-and-swap方法,将强异常安全保障回退成基本保障。
类似于木桶效应,代码是强异常安全的,还是基本异常安全的,还是没有异常安全,取决于最低层次的那个模块。换言之,哪怕只有一个地方没有考虑到异常安全,整个代码都不是异常安全的。
异常安全函数是指即使发生异常也不会泄漏资源或者允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
强烈保证往往可以通过copy-and-swap实现出来,但“强烈保证”并非对所有函数都可以实现或具备现实意义。
异常安全保证通常最高只等于其所调用之各个函数的异常安全保证中的最弱者。