(一)先看下面这些代码:
class PrettyMenu { public: void changeBackground(istream& imgSrc); private: Mutex mutex; //由于这个class希望用于多线程环境,所以它有这个互斥器作为并发控制之用 Image* bgImage; //目前的背景图像 int imageChanges; //背景图像被改变的次数 }; void PrettyMenu::changeBackground(std::istream &imgSrc) { lock(&mutex); //取得互斥器 delete bgImage; //摆脱旧的背景图像 ++imageChanges; //修改图像变更次数 bgImage = new Image(imgSrc); //安装新的背景图像 unlock(&mutex); //释放互斥器 }
上面的函数不是“异常安全性”函数,因为“异常安全性”函数所需满足的两个条件,上面这个函数都不满足。。
第一个条件:不泄露任何资源。(上面这个函数当new Image(imgSrc)发生异常的话,unlock(&mutex)就永远不会被执行,所以就资源泄漏了)。
第二个条件:不允许数据败坏。(上面这个函数当new Image(imgSrc)发生异常的话,bgImage就是指向一个已删除的对象)。
(二)
(1)解决上面那个函数泄漏资源的问题的办法:用Lock class作为一种“确保互斥器被及时释放”的方法,以对象管理资源:
void PrettyMenu::changeBackground(std::istream &imgSrc) { Lock m1(&mutex); delete bgImage; //摆脱旧的背景图像 ++imageChanges; //修改图像变更次数 bgImage = new Image(imgSrc); //安装新的背景图像 }
(2)解决上面那个函数数据败坏的办法:
首先,
异常安全函数提供以下三个保证之一:
1. 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效的状态下。
2. 强烈保证:如果异常被抛出,程序状态不改变。
3. 不抛掷保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。
对changeBackground而言,首先,从一个类型为Image*的内置指针改为一个“用于资源管理”的智能指针,第二,重新排列changeBackground内的语句次序,使得在更换图像之后再累加imageChanges。重新排列语句次序
class PrettyMenu { ... std::tr1::shared_ptr<Image> bgImage; ... }; void PrettyMenu::changeBackground(std::istream& imgSrc) { Lock ml(&mutex); bgImage.reset(new Image(imgSrc)); ++imageChanges; }
这两个改变几乎足够让changeBackground提供强烈的异常安全保证。美中不足的是参数imgSrc。如果Image构造函数抛出异常,有可能输入流的读取记号已被移走,而这样的搬移对程序其余部分是一种可见的状态改变。
(三)
copy and swap的原则:为打算修改的对象做一个副本,在那个副本上做一切必要修改。若有任何修改动作抛出异常,源对象仍然保持未改变状态。待所有改变都成功后,再将修改过的副本和原对象在一个不抛出异常的swap中置换。
实际上通常是将所有“隶属对象的数据”从原对象放进另一个对象内,然后赋予原对象一个指针,指向那个所谓的实现对象(即副本)。
实现如下:使用copy
and swap方法下的pimpl idiom手段:
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 ml(&mutex); std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl)); pNew->bgImage.reset(new Image(imgSrc)); //修改副本 ++pNew->imageChanges; swap(pImpl, pNew);//置换数据 }
请记住:
(1)异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
(2)“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
(3)函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全保证”中的最弱者。
Effective C++:条款29:为“异常安全”而努力是值得的