假设有个class用来表现夹带背景图案的GUI菜单。这个class希望用于多线程环境,所以它有个互斥器作为并发控制用:
classPrettyMenu{
public:
...
voidchangeBackgroud(std::istream& imgSrc);
...
private:
Mutexmutex; //互斥器
Image*bgImage; //目前的背景图案
intimageChanges;//背景图案被改变的次数
};
一个函数可能的实现:
void PrettyMenu::changeBackgroud(std::istream&imgSrc)
{
lock(mutex);//get mutex
deletebgImage; //delete the old
++imageChanges; //addtimes
bgImage= new Image(imgSrc); //install the new
unlock(&mutex);//free the mutex
}
从“异常安全性”的角度来讲这个函数很糟糕。“异常安全”有两个条件,它一个都没有满足。异常被抛出时,带有异常安全性的函数会:
1.不泄露任何资源试想new失败了mutex永远不会释放。
2.不允许数据败坏如果new抛出异常imageChanges会被累加。其实并没有发生改变。但是就图像已经被删除。
我们可以这样做:
void PrettyMenu::changeBackgroud(std::istream&imgSrc)
{
lockm1(&mutex); //getmutex
deletebgImage; //delete the old
++imageChanges; //addtimes
bgImage= new Image(imgSrc); //install the new
}
m1在作用域之外自动会被释放,所以不需要我们unlock。
现在我们专注数据败坏的问题。
对于changeBackground而言,提供强烈的保证几乎不困难。首先改变PrettyMenu的bgImage成员变量的类型。我们使用用于资源管理的智能指针,这是只是为了帮助我们防止资源泄露。即:tr1::shared_ptr,因为它比auto_ptr更直观的行为使它更受到欢迎。此外,我们需要在图像更改后累加。
下面的结果:
classPrettyMenu{
...
std::tr1::shared_ptr<Image>bgImage;
...
};
void PrettyMenu::changeBackgroud(std::istream&imgSrc)
{
Lockm1(&mutex);
bgImage.reset(newImage(imgSrc));
++imageChanges;
}
这里不需要手动delete旧图像,智能指针内部会处理掉。reset操作只有在new成功后才会被调用,delete只在reset函数内部使用。
以上足以让changeBackground异常安全了。美中不足的是imgSrc,如果构造函数Image抛出异常,有可能输入流的读取记号已被移走,而这样的搬移对程序多余部分是一种可见的状态改变,所以changeBackground在解决这个问题之前只提供了基本的异常安全保证。
有一个一般的策略,这个策略称为copyand swap。
structPMImpl{
std::r1::shared_ptr<Image>bgImage;
intimageChanges;
};
classPrettyMenu{
...
private:
Mutexmutex;
std::tr1::shared_ptr<PMImpl>pImpl;
};
void PrettyMenu::changeBackgroud(std::istream&imgSrc)
{
usingstd::swap;
Lockml(&mutex);
std::tr1::shared_ptr<PMImpl>pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(newImage(imgSrc));
++pNew->imageChanges;
swap(pImpl,pNew);
}
通常将所有“隶属于对象的数据”从原对象放进另外一个对象内,然后赋予原对象一个指针,指向所谓的实现对象,这种手法常被称为pimplidiom。
"copy-and-swap"策略就是对对象的状态做出"全有全无"改变的一种很好的办法,但是一般而言他并不保证整个函数有强烈的异常安全性。因为函数提供的异常安全性通常只是等于其调用各个函数的异常安全性的最弱者。