Effective-cpp-#29

Strive for exception-safe code

动机

我们常说的异常安全需要满足两个条件:

  • 不泄露任何资源
  • 不允许数据被破坏

考虑这样一段代码:

1
2
3
4
5
6
7
8
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
unlock(&mutex);
}

这段代码的问题就是,一旦“new Image(imgSrc)”出现了异常,那么unlock就不能执行了,互斥器就永远锁住了,则泄露了资源;另外就是这样bgImage会指向一个被删除的对象,而imageChanges被破坏了

解决方法

解决方法其实很简单,则使用之前提及过的条例:以对象去接管资源:

1
2
3
4
5
6
7
void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock m1(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
}

一般来说,一个异常安全函数(Exception-safe function)必须提供以下的三个保证之一:

  • 基本承诺:如果异常被抛出了,那么程序的成员应该要保持有效的状态,则没有数据被破坏了。以上面的函数为例,我们可以修改程序,在函数抛出异常时,bgImage指向一个默认的对象;

  • 强烈保证:保证程序只有两个状态:异常抛出,状态改变;否则就回到程序调用函数之前的状态;

  • nothrow保证:承诺永远不会抛出异常;


另外一个保证异常安全的方法是——copy&swap,则为你希望修改的对象构造一个副本,然后在那个副本上做一切修改,若有任何修改操作发生异常,那么原对象不受影响。待修改成功后,在交换指针。

建议

  • 异常安全函数即使发生异常也不会泄露资源或者允许任何数据结构被破坏;
  • 强烈保证可以通过copy&swap实现出来;
  • 函数提供的“异常安全保证”通常最高只等于所调用的各个函数“异常安全保证”的最弱者;