假设有一个类,代表有背景图像的GUI菜单,这个类被设计成在多线程环境中使用,所以它有一个用于并行控制的互斥体(mutex):
class PrettyMenu {
public:
...
void changeBackground(std::istream& imgSrc); // change background
... // image
private:
Mutex mutex; // mutex for this object
Image *bgImage; // current background image
int imageChanges; // # of times image has been changed
};
考虑这个PrettyMenu类的changeBackground函数的可能实现:
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage; // get rid of old background
++imageChanges; // update image change count
bgImage = new Image(imgSrc); // install new background
unlock(&mutex); // release mutex
}
如果从异常安全的观点来看,这个函数非常的烂,异常安全有两点要求,而这里全部没有满足。
当一个异常被抛出是,异常安全函数应该:
- 没有资源泄露。上面的代码没有通过这个测试,因为如果"new Image(imgSrc)"表达式产生一个异常,对unlock的调用就永远不会执行,而那个互斥体也将被永远挂起。
- 不允许数据结构恶化。如果"new Image(imgSrc)"抛出异常,bgImage被遗留下来指向一个被删除对象。另外,尽管没有将一张新的图像设置到位,imageChanges已经被增加。(在另一方面,旧的图像被明确删除,所以可能有用户看到反应之后可能会说图像已经被改变了)
规避资源泄漏问题比较容易,对这个的改进有两方面,使用c++提供的Lock机制;使用智能指针来管理新开辟的内存:
再想想,强有力的安全保证就要求如果你替换image失败,程序必须要保留被修改前一切状态,这点我们可以用swap来做,就是先保留对象最初的状态,如果新image添加成功,就替换否则放弃修改:
struct PMImpl { // PMImpl = "PrettyMenu
std::tr1::shared_ptr<Image> bgImage; // Impl."; see below for
int imageChanges; // why it's a struct
};
class PrettyMenu {
...
private:
Mutex mutex;
std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
using std::swap;
Lock ml(&mutex); // acquire the mutex
std::tr1::shared_ptr<PMImpl> // copy obj. data
pNew(new PMImpl(*pImpl));
pNew->bgImage.reset(new Image(imgSrc)); // modify the copy
++pNew->imageChanges;
swap(pImpl, pNew); // swap the new
// data into place
}
在这个例子中,我选择将 PMImpl 做成一个结构体,而不是类,因为通过让 pImpl 是 private 就可以确保 PrettyMenu 数据的封装。将 PMImpl 做成一个类虽然有些不那么方便,却没有增加什么好处。如果你愿意,PMImpl 可以嵌套在 PrettyMenu 内部,像这样的打包问题与我们这里所关心的写异常安全的代码的问题没有什么关系。
所以即使当异常被抛出的时,异常安全的函数不会泄露资源,也不允许数据结构被恶意修改,而这样的函数提供基本的,强力的,或者不抛出保证。