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

 1 class PrettyMenu{
 2 public:
 3     ...
 4     void changeBackground(std::istream& imgSrc);
 5     ...
 6 private:
 7     Mutex mutex;
 8     Image* bgImage;
 9     int imageChanges;
10 };
11 void PrettyMenu::changeBackground(std::istream& imgSrc)
12 {
13     lock(&mutex);
14     delete bgImage;
15     ++imageChanges;
16     bgImage = new Image(imgSrc);
17     unlock(&mutex);
18 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 1 class PrettyMenu{
 2     ...
 3     std::tr1::shared_ptr<Image> bgImage;
 4     ...
 5 };
 6
 7 void PrettyMenu::changeBackground(std::istream& imgSrc)
 8 {
 9     Lock ml(&mutex);
10     bgImage.reset(new Image(imgSrc));
11     ++imageChanges;
12 }

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

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

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

 1 struct PMImpl{
 2     std::tr1::shared_ptr<Image> bgImage;
 3     int imageChanges;
 4 };
 5 class PrettyMenu{
 6     ...
 7 private:
 8     Mutex mutex;
 9     std::tr1::shared_ptr<PMImpl> pImpl;
10 };
11 void PrettyMenu::changeBackground(std::istream& imgSrc)
12 {
13     using std::swap;
14     Lock ml(&mutex);
15     std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
16     pNew->bgImage.reset(new Image(imgSrc)); //修改副本
17     ++pNew->imageChanges;
18     swap(pImpl, pNew);                      //置换数据
19 }

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

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

◆总结

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

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

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

转载于:https://www.cnblogs.com/hustcser/p/4217938.html

[Effective C++ --029]为“异常安全”而努力是值得的相关推荐

  1. effective c++ 学习

    <Effective C++> 目录: 转自:http://blog.csdn.net/KangRoger/article/details/44706403 目录 条款1:视C++为一个语 ...

  2. Effective C++ --5 实现

    上一部分 Effective C++ --4 设计与声明 26.尽可能延后变量定义式的出现时间 (1)这样可以增加程序的清晰度并改善程序效率.如定义变量后还未使用遇到return或者抛出异常,这样未使 ...

  3. 万字长文带你一文读完Effective C++

    Effective C++ 视C++为一个语言联邦 STL Template C++ C Object-oriented C++ 一开始C++只是C加上一些面向对象特性,但是随着这个语言的成熟他变得更 ...

  4. 一文总结《Effective C++》

    Effective C++ 视 C++ 为一个语言联邦(C.Object-Oriented C++.Template C++.STL) 宁可以编译器替换预处理器(尽量以 const.enum.inli ...

  5. Effective C++条款粗略总结

    文章目录 Effective C++ 1.类/结构体 2.资源管理 3.实现 4.模板与泛型编程 5.定制new和delete 6.其他 Effective C++ 1.类/结构体 1.把C++看成一 ...

  6. 《Effective C++》笔记

    <Effective C++>笔记 序言 条款01:视C++为一个语言联邦 条款02:尽量以const.enum.inline替换#define 条款03:尽可能使用const 条款04: ...

  7. 【C++学习】Effective C++

    本文为Effective C++的学习笔记,第一遍学习有很多不理解的地方,后续需要复习. 0 导读 术语 声明(declaration) 告诉编译器某个东西的名称和类型,但略去细节: 每个函数的声明揭 ...

  8. [读书笔记]Effective C++ - Scott Meyers

    [读书笔记]Effective C++ - Scott Meyers 条款01:视C++为一个语言联邦 C++四个次语言: 1. C Part-of-C++,没有模板.异常.重载. 2. Object ...

  9. Effective C++ 中文版(第三版)读书笔记 更新ing~

    Effective C++ 中文版(第三版)持续更新ing 让自己习惯C++ 条款1 视c++为一个联邦 条款2 尽量以const,enum,inline替换#define 条款3 尽可能使用cons ...

最新文章

  1. 雨林木风爱好者GHOSTXP装机版_NTFS_SP3_2010_03
  2. KNN算法的机器学习基础
  3. xampp python linux,Ubuntu的XAMPP着运行python脚本
  4. Java 线程第三版 第四章 Thread Notification 读书笔记
  5. 【问链-Eos公开课】第二课 EOS环境搭建(Ubuntu系统下)
  6. git rebase/reset小计
  7. 二分查找法(递归与循环实现)
  8. vue2.0 如何自定义组件(vue组件的封装)
  9. 免费生成https证书以及配置
  10. 【高并发解决方案】高并发解决方案汇总
  11. 凯撒密码加密算法python_信息安全与密码学的关系
  12. 51单片机入门教程(4)——波形发生器
  13. python 删除指定目录_删除Python中除一个子目录外的目录
  14. 识别图片上的文字,如何在线识别?
  15. jooq 执行sql_jOOQ星期二:Markus Winand执行现代SQL任务
  16. android+蓝牙遥控器,一种通过蓝牙遥控安卓设备的方法与流程
  17. 子类继承父类,父类实现接口,子类中调用父类和接口的同名成员变量会出现歧义
  18. python可视化库matplotlib_Python可视化库matplotlib(基础整理)
  19. 构筑数字底座,同济医院用全闪存提速智慧医疗
  20. 免费开源视频会议系统Jitsi Meet自己部署记录

热门文章

  1. 【Linux系统编程】POSIX无名信号量
  2. 【Protocol Buffer】Protocol Buffer入门教程(七):导入定义
  3. java多线程notifyall_Java多线程:线程状态以及wait(), notify(), notifyAll()
  4. html 根作用域,AngularJS入门教程之Scope(作用域)
  5. android优化最强软件,最强大的安卓优化工具诞生,让手机流畅度提升75%
  6. angularjs 让当前路由重新加载_Spring Cloud Gateway的动态路由怎样做?集成Nacos实现很简单...
  7. 每天一道LeetCode-----根据中序遍历和后序遍历重构二叉树
  8. 查看某个方法在哪里被调用_MATLAB局部函数公有化的方法: localfunctions
  9. matlab中双引号_Octave、SciLab能否替代MATLAB?
  10. uC/OS II--与ECB操作相关的四个函数