1. 带有异常安全的函数要求有两个条件:1)不泄露任何资源;2)不允许数据败坏;
2. 异常安全函数提供以下三个保证:
1)基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下,没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。
2)强烈保证:如果异常被抛出,程序状态不改变。换句话说,如果函数成功,就是完全成功,如果失败,程序会回到“调用函数之前”的状态。
3)不抛掷保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。
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);
}
对于changeBackground函数不满足异常安全函数的两个条件,但是可以通过使用智能指针和调整语句顺序(事件发生后再改变状态)解决出现异常时内存泄漏和数据不一致的问题。代码如下:
class PrettyMenu{
...
std::tr1::shared_ptr<Image> bgImage;
...
};
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}
强烈保证有一个一般化的方法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);//置换数据
}
基本思想,为你打算修改的对象做一个副本,然后在副本身上做一切必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态,待所有改变成功后,再将修改过的那个副本和原对象在一个不跑出异常的操作中置换。
这种方法会有副本的存在,时间上有损耗,具体要根据情况分析,而且并不是使用了这个方法就一定可以实现强烈保证,代码如下:
void somefunc()
{
… //copy
f1();
f2();
… //swap
}
即使使用了copy-and-swap方法,如果在f1中对数据进行了修改,f2抛出异常,那么数据也是被修改过的,所以具体问题要具体分析,不能实现强烈保证,基本保证要实现。