C 箴言:争取异常安全的代码

2008-02-23 05:24:02来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折

异常安全(Exception safety)有点像怀孕(pregnancy)……但是,请把这个想法先控制一会儿。我们还不能真正地议论生育(reproduction),直到我们排除万难渡过求爱时期(courtship)。(此段作者使用的 3 个词均有双关含义,pregnancy 也可理解为富有意义,reproduction 也可理解为再现,再生,courtship 也可理解为争取,谋求。为了和后面的译文对应,故按照现在的译法。——译者注)

  假设我们有一个类,代表带有背景图像的 GUI 菜单。这个类被设计成在多线程环境中使用,所以他有一个用于并行控制(concurrency control)的互斥体(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); // acquire mutex (as in Item 14)

 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 也已被增加。(在另一方面,旧的图像被明确地删除,所以我料想您会争辩说图像已被“改变”了。)

  规避资源泄露问题比较容易,我们以前解释了如何使用对象管理资源,也讨论了引进 Lock 类作为一种时尚的确保互斥体被释放的方法:

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
 Lock ml(&mutex); // from Item 14: acquire mutex and
 // ensure its later release
 delete bgImage;
  imageChanges;
 bgImage = new Image(imgSrc);
}

  关于像 Lock 这样的资源管理类的最好的事情之一是他们通常会使函数变短。看到对 unlock 的调用不再需要了吗?作为一个一般的规则,更少的代码就是更好的代码。因为在改变的时候这样能够较少误入歧途并较少产生误解。

  随着资源泄露被我们甩在身后,我们能够把我们的注意力集中到数据结构恶化。在这里我们有一个选择,但是在我们能选择之前,我们必须先面对定义我们的选择的术语。 异常安全函数提供下述三种确保之一:

  ·函数提供基本确保(the basic guarantee),允诺假如一个异常被抛出,程式中剩下的每一件东西都处于合法状态。没有对象或数据结构被破坏,而且任何的对象都处于内部调和状态(任何的类不变量都被满足)。然而,程式的精确状态可能是不可预期的。例如,我们能够重写 changeBackground,以致于假如一个异常被抛出,PrettyMenu 对象能够继续保留原来的背景图像,或他能够持有某些缺省的背景图像,但是客户无法预知到底是哪一个。(为了查明这一点,他们大概必须调用某个能够告诉他们当前背景图像是什么的成员函数。)

  ·函数提供强力确保(the strong guarantee),允诺假如一个异常被抛出,程式的状态不会发生变化。调用这样的函数在感觉上是极其微弱的,假如他们成功了,他们就完全成功,假如他们失败了,程式的状态就像他们从没有被调用过相同。

  ·和提供强力确保的函数一起工作比和只提供基本确保的函数一起工作更加容易,因为调用提供强力确保的函数之后,仅有两种可能的程式状态:像预期相同成功执行了函数,或继续保持函数被调用时当时的状态。和之相比,假如调用只提供基本确保的函数引发了异常,程式可能存在于任何合法的状态。

  函数提供不抛出确保(the nothrow guarantee),允诺决不抛出异常,因为他们只做他们答应要做的。任何对内建类型(例如,ints,指针,等等)的操作都是不抛出(nothrow)的(也就是说,提供不抛出确保)。这是异常安全代码中必不可少的基础构件。

  假定一个带有空的异常规格(exception specification)的函数是不抛出的似乎是合理的,但这不一定正确的。例如,考虑这个函数:

int doSomething() throw(); // note empty exception spec.

  这并不是说 doSomething 永远不会抛出异常;而是说假如 doSomething 抛出一个异常,他就是个严重的错误,应该调用 unexpected 函数 [1]。实际上,doSomething 可能根本不提供任何异常确保。一个函数的声明(假如有的话,也包括他的异常规格(exception specification))不能告诉您一个函数是否正确,是否可移植,或是否高效,而且,即便有,他也不能告诉您他会提供哪一种异常安全确保。任何这些特性都由函数的实现决定,而不是他的声明能决定的。

  [1] 关于 unexpected 函数的资料,能够求助于您中意的搜索引擎或包罗万象的 C 课本。(您或许有幸搜到 set_unexpected,这个函数用于指定 unexpected 函数。)

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C语言基础教程(二)数据类型、变量和运算符

下一篇: Bjarne:为什么不能为模板参数定义约束?