假设有个class用来表现夹带背景图案的GUI菜单单,这个class用于多线程环境,所以它有个互斥器(mutex)作为并发控制用:

 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);
}

从异常安全性的角度看,这个函数很糟。因为没有满足异常安全的两个条件:

1.不泄露任何资源。上述代码没有做到这一点,因为一旦“new Image(imgSrc)”导致异常,对unlock就不会执行,于是互斥器就永远被把持住了。

2.不允许数据破坏。如果“new Image(imgSrc)”抛出异常,bgImage就指向一个已被删除的对象,imageChanges也已被累加,而其实并没有新的图像被成功安装起来。

解决资源泄漏的问题很容易,因为条款13已经教会我们“以对象去管理资源”,而条款14也逃入了Lock class作为一种“确保互斥器被及时释放”的方法:

 void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock ml(&mutex); //来自条款14;
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
}

关于“资源管理类”如Lock,一个最棒的事情是,它们通常使函数更短。较少的代码就是较好的代码,因为出错的机会比较少。

异常安全函数(Exception-safe function)提供以下三个保证之一:

1.基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足)。然而程序的现实状态恐怕不可预料。如上例changeBackground使得一旦有异常被抛出时,PrettyMenu对象可以继续拥有原背景图像,或是令它拥有某个缺省背景图像,但客户无法预期哪一种情况。如果想知道,它们恐怕必须调用某个成员函数以得知当时的背景图像是什么。

2.强烈保证:如果异常被抛出, 程序状态不改变。如果函数成功,就是完全成功,否则,程序会回复到“调用函数之前”的状态。

3.不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。作用于内置类型(如ints,指针等等)上的所有操作都提供nothrow保证。带着“空白异常明细”的函数必为nothrow函数,其实不尽然

 int doSomething() throw(); //”空白异常明细”

这并不是说doSomething绝不会抛出异常,而是说如果抛出异常,将是严重错误,会有你意想不到的函数被调用。实际上doSomething也许完全没有提供任何异常保证。函数的声明式(包括异常明细)并不能告诉你是否它是正确的、可移植的或高效的,也不能告诉你它是否提供任何异常安全性保证。

一般而言,应该会想提供可实施的最强烈保证。nothrow函数很棒,但我们很难再c part of c++领域中完全没有调用任何一个可能抛出异常的函数。所以大部分函数而言,抉择往往落在基本保证和强烈保证之间。

对changeBackground而言,首先,从一个类型为Image*的内置指针改为一个“用于资源管理”的智能指针,第二,重新排列changeBackground内的语句次序,使得在更换图像之后再累加imageChanges。

 class PrettyMenu{
...
std::tr1::shared_ptr<Image> bgImage;
...
}; void PrettyMenu::changeBackground(std::istream& imgSrc)
{
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
}

不再需要手动delete旧图像,只有在reset在其参数(也就是“new Image(imgSrc)”的执行结果)被成功生成之后才会被调用。美中不足的是参数imgSrc。如果Image构造函数抛出异常,有可能输入流的读取记号(read marker)已被移走,而这样的搬移对程序其余部分是一种可见的状态改变。所以在解决这个之前只提供基本点异常安全保证。

作为策略,桥接模式或者叫做PIMPL的模式可以实现:

PIMPL模式可以参考我的C++博客。

 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); //置换数据
}

在那个副本上做一切必要修改。若有任何修改动作抛出异常,源对象仍然保持未改变状态。待所有改变都成功后,再将修改过的副本和原对象在一个不抛出异常的swap中置换

实现上通常是将所有“隶属对象的数据”从原对象放进另一个对象内,然后赋予源对象一个指针,指向那个所谓的实现对象(implementation object,即副本)。

◆总结

1.异常安全函数(Exception-safe functions)即时发生异常也不会泄露资源或允许任何数据结构破坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。

2.“强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。

3.函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。

[Effective C++ --029]为“异常安全”而努力是值得的的更多相关文章

  1. Effective C++ -----条款29:为“异常安全”而努力是值得的

    异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏.这样的函数区分为三种可能的保证:基本型.强烈型.不抛异常型. “强烈保证”往往能够以c ...

  2. Effective C++ Item 29 为”异常安全”而努力是值得的

    本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 经验:异常安全函数即使发生异常也不会泄漏资源或同意不论什么数据结构败坏.这种函数区分为三种 ...

  3. Effective C++(Third Edition) Item29 为“异常安全”而努力是值得的

    “异常安全”有两个条件: 1.不泄露任何资源 可以通过以对象管理资源的方式(Item13). 2.不允许数据败坏 异常安全函数提供以下三种保证之一 a.基本承诺 如果异常被抛出,程序内的任何事物都仍然 ...

  4. Effective C++:条款29:为“异常安全”而努力是值得的

    (一)先看以下这些代码: class PrettyMenu { public: void changeBackground(istream& imgSrc); private: Mutex m ...

  5. 读书笔记_Effective_C++_条款二十九:为“异常安全”而努力是值得的

    还是举书上的例子: void PrettyMenu::changeBackground(std::istream& imgSrc) { lock(&mutex); delete bgI ...

  6. 条款29:为“异常安全”而努力是值得的

    当异常被抛出时,带有异常安全性的函数: 1.不泄露任何资源 2.不允许数据败坏   异常安全函数提供以下三个保证之一: 1.基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效的状态下.没有任何对 ...

  7. [Effective Java]第九章 异常

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  8. Effective java 系列之异常转译

    异常转译:当位于最上层的子系统不需要关心底层的异常细节时,常见的作法时捕获原始异常,把它转换一个新的不同类型的异常,在将新异常抛出. 通常方法捕获底层异常,然后抛高层异常. public static ...

  9. 《Effective C++》第三版笔记

    阅读此笔记前,请先阅读 <Effective C++>第二版笔记  和  <More Effective C++>笔记 这里只记录与上面笔记不同的条款,主要是 "面对 ...

随机推荐

  1. javascript 面向对象整理

    整理一下js面向对象中的封装和继承. 1.封装 js中封装有很多种实现方式,这里列出常用的几种. 1.1 原始模式生成对象 直接将我们的成员写入对象中,用函数返回. 缺点:很难看出是一个模式出来的实例 ...

  2. mysql操作时遇到的小问题

    mysql数据库在程序中执行sql语句时,或者在执行sql时,数据库表可能会有一些特殊的字符,比如说#,.等,这样在执行时 可能会遇到问题 如以下的表名,backup_2014.2.22, 这个表在查 ...

  3. J2EE到底是什么

    目前所有的B/S系统应用可以分为:有状态(statefull)和无状态(stateless)两大类别. 有状态是指在整个系统的处理过程中要保留记住一些信息,而无状态则相反,每次request都是独立的 ...

  4. 【转】Maven实战(八)---模块划分

    本博文出自于:http://blog.csdn.net/liutengteng130/article/details/47000217    感谢! 为了防止传递依赖,我们各个模块之间尽量用直接依赖的 ...

  5. DelphiXE7操作sqlite数据库

    准备工作: 1.用SQLiteExpertPers建立一个sqlite数据库. 2.打开delphi xe7. 一.FireDAC法 设置库联接 1.放入FDConnection1控件 2.放入FDC ...

  6. UVALive 7281 Saint John Festival (凸包+O(logn)判断点在凸多边形内)

    Saint John Festival 题目链接: http://acm.hust.edu.cn/vjudge/contest/127406#problem/J Description Porto's ...

  7. HDU 5762 Teacher Bo (暴力)

    Teacher Bo 题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5762 Description Teacher BoBo is a geogra ...

  8. hdu 5640 King's Cake(BestCoder Round #75)

    King's Cake Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total ...

  9. hive 子查询特别分析

      Hive只支持在FROM子句中使用子查询,子查询必须有名字,并且列必须唯一:SELECT ... FROM(subquery) name ... 确认下是否一定要求列必须唯一?      建表语句 ...

  10. c# 如何使用DLL的config文件中的信息

    我知道用c#编写的exe程序可以读取config文件中的配置信息,比如Test.exe,可以在与Test.exe相同目录下放置一个config文件:Test.exe.config,用System.Co ...