29 为“异常安全”而努力是值得的
一、异常安全函数提供以下三个保证
- 基本承诺:如果异常被抛出,程序内的任何事务仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足)。如果含函数出现异常,程序有可能处于任何状态——只要那是个合法状态。
- 强烈保证:如果异常抛出,程序状态不改变。调用这样的函数需要有这样的认知:如果函数成功,就是完全成功,如果函数失败,程序会回复到“调用函数之前”的状态。
- 不抛异常保证:承诺不抛出异常,因为他们总是能够完成他们原先承诺的功能。作用于内置类型(如指针等等)身上的所有操作都提供nothrow保证。
二、应该提供何种异常安全函数
- 尽可能的实施最高等级的异常安全保证。只有当调用没有异常安全的传统代码,才可被迫”无任何保证“。
- 强烈保证并非对所有函数都可实现或具备实现意义。
- 函数的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
- 通常在写新代码是应提供异常安全基本承诺。
2.1 nothrow
作用于内置类型身上的所有操作都提供nothrow保证。
2.2 强烈保证
- 使用对象管理资源。如智能指针管理裸指针、使用Lock对象管理互斥锁
- 调整语句词语
- copy and swap 策略 为你打算修改的对象(原件)做出一个副本,然后在那副本身上做一些必要修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛异常的操作中置换(swap)。
copy and swap 策略是对对象状态做出“全有或全无”改变的一个很好办法,但是并不保证整个函数具有强烈的异常安全性。
2.3 基本承诺
强烈保证的前两条。
2.4 示例
- 基本承诺 示例
class PrettyMenu{
public:
...
void changeBackground(std::istream& imgSrc);
...
private:
Mutex mutex;
std::tr1::shared_ptr<Image> bgImage;
int imageChanges;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
Lock ml(&mutex);
bgImage.reset(new Image(imgsrc)); //如果Image构造函数抛出异常,有可能输入流的读取记号已被移走,而这样的搬移对程序其余部分是一种可见状态改变。所以该函数只提供了基本的异常安全保证。
++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)); //创建pImpl的副本
pNew->bgImage.reset(new Image(imgsrc)); //修改pImpl的副本
++pNew->imageChanges; //修改pImpl的副本
swap(pImpl,pNew); //交换副本和原对象
}
28 避免返回handles对象内部成分
- reference、指针或迭代器就是所谓的handles
- 成员变量的封装性最多只等于“返回其reference”的函数的访问级别。
- 内部对象不光包括成员变量,还包括private和protected访问级别的成员函数。
- 避免返回handles对象内部成分的原因
- 使const成员函数的行为更像一个const
const成员函数传出一个reference,后者所指数据与对象自身有关联,而它有被存储于对象之外,那么这个函数的调用者可以修改那笔数据。 - 返回const 的handles虽然外接不能更改,但却可能导致空悬。
- 使const成员函数的行为更像一个const