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);
}
异常安全有两个条件:
1.不泄露任何资源。上述代码一旦“new Image(imgSrc)"导致异常,对unlock的调用就绝不会执行,于是互斥器就永远呗把持住了。
2.不允许数据败坏。如果“new Image(ImgSrc)"抛出异常,bgImage就是指向一个已被删除的对象,imageChanges已被累加,而其实没有新的图像被成功安装起来。
解决资源泄漏的问题很简单:
因为条款13讨论过如何以对象管理资源,而条款14也导入Lock class作为一种“确保互斥器被及时释放”的方法:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock ml(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
}
我们现在专注解决数据的败坏了。
1.首先改变PrettMenu的bgImage成员变量的类型,从一个类型为Image* 的内置指针改为一个“用于资源管理”的智能指针。
以对象(例如智能指针)管理资源是良好设计的根本。
2.重新排列chageBackground内的语句次序,使得在更换图像之后才累加 Image Changes.
class PrettyMenu
{
...
std::tr1::shared_ptr<Image> bgImage;
...
};
void PrettyMenu::chageBackground(std::istream & imgSrc)
{
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}
注意,这里不再需要的手动delete旧图像,因为这个动作已经由智能指针内部处理掉了。此外,删除动作只发生在新图像被成功创建后。更正确地说,tr1::shared_ptr::reset函数只有在其参数(也就是“new Image(imgsrc)"的执行结果)被成功生成之后才会被调用。delete只在reset函数内被使用,所以如果从未进入那个函数也就绝不会使用delete。
-------------------------------------------------------------------------------------------------------------------------------------
有一个一般化的设计策略被称为copy and swap。原则很简单:为你打算修改的对象做出一份副本,然后在那副本身上做一切必要修改,若修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换。
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);
}