Effective C++条款29:为“异常安全”而努力是值得的(Strive for exception-safe code)

news2025/1/13 10:42:01

Effective C++条款29:为“异常安全”而努力是值得的(Strive for exception-safe code)

  • 条款29:为“异常安全”而努力是值得的
    • 1、抛出异常的案例
    • 2、解决资源泄露的问题
    • 3、异常安全的三种保证
    • 4、两种解决异常安全的方法
      • 4.1 使用智能指针
      • 4.2 拷贝和交换
    • 5、牢记
  • 总结


《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:

第5章:实现

在这里插入图片描述


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

1、抛出异常的案例

  假设我们有一个表示GUI菜单的类,这个GUI菜单有背景图片。这个类将被使用在多线程环境中,所以需要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); //释放互斥器
}

上面的changeBackground()成员函数不是“异常安全的”。因为异常安全的函数应该有以下两种特性:

  • ① 不泄露任何资源:上述的代码如果new Image()操作导致异常,那么就永远不会调用unlock,那么互斥器将永远被锁住。
  • ② 不允许数据破坏:如果new Image()操作导致异常,那么bgImage已经被删除了,而且imageChanges数量也被累加了,所以资源被改变了。

2、解决资源泄露的问题

  这个问题很容易解决,在条款13中讨论了如何以对象管理资源,条款14也介绍了自己设计一个名为Lock的类来管理互斥器,定义如下:

//修改后PrettyMenu的成员函数
void PrettyMenu::changeBackground(std::istream& imgSrc) {
    Lock ml(&mutex);//将互斥器封装在类中进行管理
    delete bgImage; //删除旧图片
    ++imageChanges; //修改图像更改次数
    bgImage = new Image(imgSrc); //安装新的背景图片
}

  使用像Lock一样的资源管理类的一个极大的好处是它通常使函数更短。比如为什么不再需要对unlock的调用了?作为一个通用的规则,代码越少越好,因为对代码做改动时,出错和理解错误的可能性变低了。

3、异常安全的三种保证

  接下来让我们看一看数据结构被损坏的问题。这里我们要做出选择,但是在我们可以进行选择之前,必须对定义这些选择的术语做一下比较。

异常安全的函数提供了如下三种保证的一种:

  • ① 基本承诺:如果异常被抛出,程序内的任何事物都应该保持在有效状态

  没有对象或者数据被损坏,并且所有对象或者数据保持一个内部一致的状态(例如所有类的约束条件继续被满足)。然而,程序的正确状态可能不能够被预测出来。例如,我们可以实现changeBackground,一旦异常被抛出,PrettyMenu对象可以继续拥有旧的背景图片,但是客户不能够预测拥有的是哪一个。(为了能够找出这个图片,大概需要调用一些成员函数,来告诉它们当前的背景图片是哪一个)

  • ② 强烈保证:如果程序抛出异常,程序状态不应该保证。调用这样的函数应该保证:如果函数成功就是完全成功;如果函数执行失败,程序会恢复到“调用函数之前”的状态。

  使用提供强保证的函数比只提供基本保证的函数要更加容易,因为调用提供强保证的函数之后,只可能有两种程序状态:函数被正确执行后的状态,或者函数被调用之前的状态。而如果在调用只提供基本保证的函数的时候抛出异常,程序可以进入任何有效状态。

  • 不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。

  所有在内置类型上进行的操作都是无异常的。这是异常安全代码的一个关键的构建基础。

  认为带有空异常明细(empty exception specification)的函数是无异常的,这可能看上去是合理的,但事实上不是这样。举个例子,如: int doSomething() throw();//空白的异常明细
  这并不是说doSomething永远不会抛出异常。它的意思是如果soSomething抛出异常,就会是一个严重的错误,并且会调用意料不到的函数。事实上,doSomething没有提供任何异常安全保证。这个函数的声明(如果有异常明细,也包含异常明细)并没有告诉你这个函数是否是正确的,可移植的或者效率高的,也没有为你提供任何异常安全保证。所有这些特性都由函数的实现来决定,而不是声明。

NOTE:异常安全的代码必须提供上面三种保证的一种。如果没有提供,它就不是异常安全的。

4、两种解决异常安全的方法

4.1 使用智能指针

  对于changeBackground来说,提供强烈保证不是多难的事。

  首先,我们将PrettyMenu的bgImage数据成员的类型从内建的Image*指针替换为一种资源管理智能指针(见条款13)。说真的,对于防止资源泄露来说这绝对是一个好方法。它帮我们提供强异常安全保证的事实只是简单对条款13中的论述(使用对象管理资源是好的设计的基础)做了进一步的加强。在下面的代码中,我将会展示tr1::shared_ptr的使用,因为当进行拷贝时使用tr1::shared_ptr比使用auto_ptr更加直观,更受欢迎。

  第二,我们对changeBackground中的语句进行重新排序,达到只有image被修改的时候才会增加imageChnages的目的。作为通用准则,一个对象的状态没有被修改就表明一些事情没有发生。

class PrettyMenu {
...
private:
    std::tr1::shared_ptr<Image> bgImage;
};
//修改后的PrettyMenu的成员函数
void PrettyMenu::changeBackground(std::istream& imgSrc) {
	Lock ml(&mutex);
	bgImage.reset(new Image(imgSrc));//以"new Image"的执行结果
	++imageChanges;//设定bgImage内部指针
}

  注意这里不再需要手动delete旧图像,因为这由智能指针在内部处理。并且,销毁操作只有在新image成功创建的时候才会发生。更精确的说,只有在参数(new Image(imgSrc)的结果)被成功创建的时候tr1::shared_ptr::reset函数才会被调用。delete只在reset函数内部被使用,所以如果reset不被调用,delete永远不会被执行。注意资源管理对象的使用再次削减了changeBackground的长度。

  正如我所说的,上面的两个修改足以为changeBackground提供强异常安全保证。还有美中不足的就是关于参数imgSrc。如果Image的构造函数抛出异常,输入流的读标记可能会被移动,这个移动致使状态发生变化并且对程序接下来的运行是可见的。如果changeBackground不处理这个问题,它只能提供基本异常安全保证。

4.2 拷贝和交换

  “3”中只解决了基本保证,但在changeBackground()函数中,Image的构造函数可能会抛出异常,也就是无法提供强烈保证。有个一般化的设计策略很典型地会导致强烈保证,很值得熟悉它。这个策略就是copy and swap。

copy and swap策略的原则是:为你打算修改的对象(原件)做一份副本,然后在副本身上做修改:

  • 如果在副本的身上修改抛出了异常,那么原对象未改变状态。

  • 如果在副本的身上修改未抛出异常,那么就将修改过的副本与原对象进行置换(swap)。

  实际上通常是将所有“隶属对象的数据”从原对象放进另一个对象内,然后赋予原对象一个指针,指向那个所谓的实现对象(即副本)。这种收发被称为“pimpl idiom”,在条款31中会进行描述。对于上面的PrettyMenu来说,典型的写法如下:

struct PMImpl {//将bgImage和imageChanges从PrettyMenu独立出来,封装成一个结构体
    std::tr1::shared_ptr<Image> bgImage;
    int imageChanges
};
class PrettyMenu {
    //...
private:
    std::tr1::shared_ptr<PMImpl> pImpl; //创建一个该结构
};
void PrettyMenu::changeBackground(std::istream& imgSrc) {
    using std::swap; //见条款25
    Lock ml(&mutex);
    //以pImpl为原件,创建一个副本,然后在副本上做修改
    std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
    pNew->bgImage.reset(new Image(imgSrc));
    pNew->imageChanges++;
    //如果上面副本的修改没有抛出异常,那么交换副本与原件
    swap(pImpl, pNew);
}

  在这个例子中,我选择将PMImpl定义为一个结构体而不是类,因为PrettyMenu的封装性通过pImpl的私有性(private)来保证。把PMImpl定义成类会至少和结构体一样好,虽然有一些不方便。如果需要,PMImpl可以被放在PrettyMenu中,但是打包问题(packaging)是我们所牵挂的。

5、牢记

  • 异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构破坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛出异常型。

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

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

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/74744.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何压缩动态图片大小?gif图太大了怎么压缩?

对于新媒体行业人员来说&#xff0c;平时在工作中需要存非常多的素材&#xff0c;这些素材中有很多就是gif格式的&#xff0c;随着积累的素材越来越多&#xff0c;这些素材会占用大量的储存空间&#xff0c;那么遇到这种情况应该怎么办呢&#xff1f;应该如何压缩动态图片大小&…

Flutter - 布局原理与约束(constraints)

尺寸限制类容器用于限制容器大小&#xff0c;Flutter中提供了多种这样的容器&#xff0c;如ConstrainedBox、SizedBox、UnconstrainedBox、AspectRatio 等 1 ConstrainedBox ConstrainedBox 用于对子组件添加额外的约束 一般作为最外层的父布局 2 BoxConstraints BoxConstrai…

[附源码]Python计算机毕业设计SSM基于社区人员管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

阿里P8架构师精心整理:Dubbo+Docker+Kubernetes实战PDf,附面试题

前言 学习是一种基础性的能力。然而&#xff0c;“吾生也有涯&#xff0c;而知也无涯。”&#xff0c;如果学习不注意方法&#xff0c;则会“以有涯随无涯&#xff0c;殆矣”。 学习就像吃饭睡觉一样&#xff0c;是人的一种本能&#xff0c;人人都有学习的能力。我们在刚出生…

Keycloak之17.0.1 版本和Gerrit 整合-yellowcong

通过keycloak 来实现gerrit的用户管理。主要有几个步骤,1.安装gerrit,2.安装gerrit oauth 插件,3.配置gerrit . 4.创建keycloak的配置,添加realm,client,user ,三个,5.重启gerrit 测试。 17版本不一样的是,需要开启oauth,服务器增加前缀。 准备 Keycloak之17.0.1 版本安…

43. Python for 循环

43. Python for 循环 文章目录43. Python for 循环1. 课题导入2. 什么是循环3. 什么是for循环4. for 循环语法5. 可迭代对象6. for循环的执行流程7. for 循环的对象1. 循环对象为字符串2. 循环的对象不能为整数3. 循环的对象不能为浮点数4. 循环对象为布尔类型5. 循环对象为列表…

使用docker构建vue项目并成功运行在本地和线上

先说本地环境 windows10 node vue docker都已经安装齐全 获取nginx镜像 因为要用这个镜像来构建你的vue项目&#xff0c;就像给vue项目提供一个环境一样 docker pull nginx 创建 nginx config配置文件 在项目根目录下创建文件default.conf server {listen 80;s…

火灾报警产品-火灾探测报警产品

消防产品&#xff0c;是指专门用于火灾预防、灭火救援和火灾防护、避难、逃生的产品。适用范围 适用于消防联动控制系统设备、防火卷帘控制器、线型感温火灾探测器、城市消防远程监控产品。认证模式 型式试验初始工厂检查获证后监督。申请资料 1.认证委托人/生产者/生产企业的资…

全面支持 PyTorch 2.0:BladeDISC 5 月~11 月新功能发布

作者&#xff1a;BladeDISC研发团队 BladeDISC 上一次更新主要发布了 GPU AStitch 优化&#xff0c;方法来源于我们发表在 ASPLOS 2022上的论文AStitch。这一次&#xff0c;我们发布了 0.3.0 版本。 本次更新中 BladeDISC 社区全面支持了 PyTorch 2.0 编译&#xff0c;推进了…

同城跑腿系统搭建,灵活的配送选择满足更多场景

为了提供更加便捷的生活服务&#xff0c;同城跑腿系统搭建通过线上的同城跑腿服务平台&#xff0c;在网上用户可以申请同城服务的需求&#xff0c;平台的相关的工作人员快速的响应接单&#xff0c;快速进行同城的配送跑腿服务。 同城跑腿系统搭建&#xff0c;功能少是万万不能…

微信小程序第四篇:生成图片并保存到手机相册

系列文章传送门&#xff1a; 微信小程序第一篇&#xff1a;自定义组件详解 微信小程序第二篇&#xff1a;七种主流通信方法详解 微信小程序第三篇&#xff1a;获取页面节点信息 目录 一、封装分享组件 二、定义用户授权方法 三、调用流程 首先我们看一下要完成的效果&#x…

地理空间开发包 TatukGIS Developer Kernel 11.72.X Crack

TatukGIS Developer Kernel (DK) 是专业级 GIS SDK&#xff08;软件开发工具包&#xff09;&#xff0c;各行各业的客户都使用它来开发自定义 GIS 应用程序或向现有产品添加地理空间功能。DK 可作为多个 SDK 版本使用&#xff0c;每个版本都针对特定的开发平台进行本地编译&…

胡扯系列之私人AI助手系统的分析与设计

背景 随着时代的发展&#xff0c;计算机算力的提升和近些年来AI模型的井喷以及发展。人工智能应用已经深入我们的日常生活。如人脸识别&#xff0c;无人驾驶等等&#xff0c;同时为了更好地与用户进行交互&#xff0c;完成特定功能&#xff0c;智能对话助手应运而生。如今大量…

某宝付费买的价值上万的60G的Python学习资源,0基础轻松赚钱到手软,请低调使用,禁止外传

前言 你是否 还在为升职加薪发愁&#xff1f; 苦于领导看不到自己更多长处&#xff1f; 还在为房贷&#xff0c;车贷&#xff0c;生计而发愁&#xff1f; 苦于不上班如何轻松赚快钱补贴家用&#xff1f; 为了帮助财务、设计、运营、策划、销售、HR、金融从业者、电商从业…

【单目3D目标检测】MonoFlex论文精读与代码解析

文章目录PrefaceAbstractContributionsPipelineProblem DefinitionDecoupled Representations of ObjectsInside & Outside ObjectsEdge FusionLossVisual Properties Regression2D DetectionDimension EstimationOrientation EstimationKeypoint EstimationAdaptive Depth…

Docker网络模式与配置

目录 &#x1f388;&#x1f388;1. Docker网络模式&#x1f3c3;‍♂️&#x1f3c3;‍♂️ &#x1f3c3;‍♂️&#x1f3c3;‍♂️2. 外部访问docker容器&#x1f3c3;‍♂️&#x1f3c3;‍♂️ &#x1f388;&#x1f388;3. 创建自定义网络&#xff1a;&#xff08;设…

尚医通 (三十一) --------- 手机登录

目录一、登录需求1. 登录效果2. 登录需求二、登录1. 搭建 service-user 模块2. 添加用户基础类3. 登录 API 接口4. 生成 token5. 阿里云短信6. 登录前端7. 登录全局事件8. myheader.vue 完整代码三、用户认证与网关整合一、登录需求 1. 登录效果 2. 登录需求 ① 登录采取弹出…

Python爬虫实战,requests+time模块,爬取某招聘网站数据并保存csv文件(附源码)

前言 今天给大家介绍的是Python爬取某招聘网站数据并保存本地&#xff0c;在这里给需要的小伙伴们代码&#xff0c;并且给出一点小心得。 首先是爬取之前应该尽可能伪装成浏览器而不被识别出来是爬虫&#xff0c;基本的是加请求头&#xff0c;但是这样的纯文 本数据爬取的人会…

ImmunoChemistry丨艾美捷抗体夹心ELISA开发试剂盒说明书

使用ImmunoChemistry艾美捷抗体夹心ELISA开发试剂盒评估检测可行性并优化ELISA性能参数。抗体夹心ELISA开发试剂盒提供了八种特殊配制的ELISA溶液和一个模板&#xff0c;用于抗体夹心ELISA测试的初始开发和优化。全面的ELISA开发手册提供了评估初始检测可行性和优化ELISA性能参…

关于 npm run buildprod 报错问题 :文件名、目录名或卷标语法不正确

引言 vue开发基本完成后进行打包时出现错误&#xff0c;这个错误以前没有遇到过&#xff0c;所以在这里激励 参考文章1 参考文章2 问题描述 在idea中运行npm run build:prod打包vue时出现报错 E:\Allworkspaces\idea-workspace\Project\vue-project\my-blog\vue-admin-te…