[翻译] Effective C++, 3rd Edition, Item 29: 争取 exception-safe code(异常安全代码)(下)

从CPRI到eCPRI:手把手教你用Wireshark分析基站前传协议帧结构 本文手把手教你使用Wireshark工具,深入解析从CPRI到eCPRI的基站前传协议帧结构。通过搭建分析环境、编写解析脚本,并对比两者在时延管理、帧格式及数据封装上的差异,为无线通信领域的研发与测试人员提供了一套实用的逆向分析与故障排查方法。 阅读详情

(点击此处,接上篇)

然而,让我们把它放在一边,并且依然假装 changeBackground 可以提供 strong guarantee(强力保证)。(我确信你至少能提出一种方法让它做到这一点,或许可以通过将它的参数类型从一个 istream 变成包含图像数据的文件的文件名。)有一种典型的产生 strong guarantee(强力保证)的通用设计策略,而熟悉它是非常必要的。这个策略被称为 "copy and swap"。在原理上,它很简单。先做出一个你要改变的 object 的拷贝,然后在这个拷贝上做出全部所需的改变。如果改变过程中的某些操作抛出了异常,原来的 object 保持不变。在所有的改变全部成功之后,将原来的和被改变的 object 和在一个 non-throwing(不抛出)的操作中进行交换。

这通常通过这种方法实现:将整个对象的全部数据从“真正的” object 中放入到一个单独的执行 object 中,然后将一个指向执行 object 的指针交给真正的 object。这通常被称为 "pimpl idiom",Item 31 描述了它的一些细节。对于 PrettyMenu 来说,它一般就像这样:

struct PMImpl {                               // PMImpl = "PrettyMenu
  std::tr1::shared_ptr<Image> bgImage;        // Impl."; see below for
  int imageChanges;                           // why it's a struct
};

class PrettyMenu {
  ...

private:
  Mutex mutex;
  std::tr1::shared_ptr<PMImpl> pImpl;
};

void PrettyMenu::changeBackground(std::istream& imgSrc)
{
  using std::swap;                            // see Item 25

  Lock ml(&mutex);                            // acquire the mutex

  std::tr1::shared_ptr<PMImpl>                // copy obj. data
    pNew(new PMImpl(*pImpl));

  pNew->bgImage.reset(new Image(imgSrc));     // modify the copy
  ++pNew->imageChanges;

  swap(pImpl, pNew);                          // swap the new
                                              // data into place

}                                             // release the mutex

在这个例子中,我选择将 PMImpl 做成一个 struct,而不是 class,因为通过让 pImpl 是 private 就可以确保 PrettyMenu 数据的封装。将 PMImpl 做成一个 class 尽管少了一些便利性,却没有增加什么好处。(这也会使有 object-oriented 洁癖者走投无路。)如果你愿意,PMImpl 可以嵌套在 PrettyMenu 内部,像这样的打包问题与我们这里所关心的写 exception-safe code(异常安全代码)之间没有什么关系。

copy-and-swap 策略是一种要么全部改变,要么丝毫不变一个 object 的状态的极好的方法,但是,在通常情况下,它不能保证全部函数都是 strongly exception-safe(强力异常安全)的。为了弄清原因,考虑一个 changeBackground 的抽象化身—— someFunc,它使用了 copy-and-swap,但是它包含了对另外两个函数(f1 和 f2)的调用:

void someFunc()
{
  ...                                     // make copy of local state
  f1();
  f2();
  ...                                     // swap modified state into place
}

很明显,如果 f1 或 f2 低于 strongly exception-safe(强力异常安全),someFunc 就很难成为 strongly exception-safe(强力异常安全)的。例如,假设 f1 仅提供 basic guarantee(基本保证)。为了让 someFunc 提供 strong guarantee(强力保证),它必须写代码在调用 f1 之前测定整个程序的状态,并捕捉来自 f1 的所有异常,然后恢复到原来的状态。

即使 f1 和 f2 都是 strongly exception safe(强力异常安全)的,事情也好不到哪去。毕竟,如果 f1 运行完成,程序的状态已经发生了毫无疑问的变化,所以如果随后 f2 抛出一个异常,即使 f2 没有改变任何东西,程序的状态也已经和调用 someFunc 时不同。

问题在于副作用。只要函数仅对局部状态起作用(例如,someFunc 仅仅影响调用它的那个 object 的状态),它提供 strong guarantee(强力保证)就相对容易。当函数有作用于非局部数据的副作用,它就会困难得多。例如,如果调用 f1 的副作用是一个数据库被改变,让 someFunc 成为 strongly exception-safe(强力异常安全)就非常困难。一般情况下,没有办法撤销已经提交的数据库变化,其他数据库客户可能已经看见了数据库的新状态。

类似这样的问题可能会阻止你为一个函数提供 strong guarantee(强力保证),即使你希望去做。另一个问题是性能。copy-and-swap 的要点是这样一个想法:改变一个 object 的数据的拷贝,然后在一个 non-throwing(不抛出)的操作中将原来的和被改变的数据进行交换。这就需要做出每一个将被改变的 object 的拷贝,这可能会用到你不能或不情愿动用的时间和空间。strong guarantee(强力保证)是非常值得的,当它可行时你应该提供它,除非在它不能 100% 可行的时候。

当它不可行时,你就必须提供 basic guarantee(基本保证)。在实践中,你可能会发现你能为某些函数提供 strong guarantee(强力保证),但是性能和复杂度的成本使得它难以用于大量的其它函数。只要你做过只要可行就提供 strong guarantee(强力保证)的合理的努力,当你只提供了 basic guarantee(基本保证)时,就没有人会因此而站在批评你的立场上。对于很多函数来说,basic guarantee(基本保证)是一个完全合理的选择。

如果你写了一个根本没有提供 exception-safety guarantee(异常安全保证)的函数,事情就不同了,因为在这一点上有罪推定是合情合理的,直到你证明自己是清白的。你应该写出 exception-safe code(异常安全代码)。除非你能做出有说服力的答辩。请再次考虑调用了函数 f1 和 f2 的 someFunc 的实现。假设 f2 根本没有提供 exception safety guarantee(异常安全保证),甚至没有 basic guarantee(基本保证)。这就意味着如果 f2 发生一个异常,程序可能会在 f2 内部泄漏资源。这也意味着 f2 可能会破坏数据结构,例如,有序数组可能不再有序,正在从一个数据结构传递给另一个数据结构去的 objects 可能会丢失,等等。没有任何办法可以让 someFunc 能弥补这些问题。如果 someFunc 调用的函数不提供 exception-safety guarantees(异常安全保证),someFunc 本身就不能提供任何保证。

请允许我回到怀孕。一个女性或者怀孕或者没有。部分地怀孕是绝不可能的。与此相似,一个软件系统或者是 exception-safe(异常安全)的或者不是。没有像 partially exception-safe system(部分异常安全系统)这样的东西。一个系统即使只有一个独立函数不是 exception-safe(异常安全)的,那么系统作为一个整体就不是 exception-safe(异常安全)的,因为调用那个函数可能导致泄漏资源和破坏数据结构。不幸的是,很多 C++ 的遗留代码在写的时候没有留意 exception safety(异常安全),所以现在的很多系统都不是 exception-safe(异常安全)的。它们混合了用 exception-unsafe(非异常安全)的风格书写的代码。

没有理由让事情的这种状态永远持续下去。当书写新的代码或修改已有代码时,要仔细考虑如何使它 exception-safe(异常安全)。从使用 objects 管理资源开始(还是参见 Item 13)。这样可以防止资源泄漏。接下来,决定三种 exception safety guarantees(异常安全保证)中的哪一种是你能够为你写的每一个函数实际提供的最强的保证,只有当你不调用遗留代码就别无选择的时候,才能满足于没有保证。既为你的函数的客户也为将来的维护人员,文档化你的决定。一个函数的 exception-safety guarantee(异常安全保证)是它的接口的可见部分,所以你应该谨慎地选择它,就像你谨慎地选择一个函数接口的其它方面。

四十年前,到处都是 goto 的代码被尊为最佳实践。现在我们为书写结构化控制流程而奋斗。二十年前,全局可访问数据被尊为最佳实践。现在我们为封装数据而奋斗,十年以前,写函数时不必考虑异常的影响被尊为最佳实践。现在我们为写 exception-safe code(异常安全代码)而奋斗。

时光在流逝。我们生活着。我们学习着。

Things to Remember

  • 即使当异常被抛出时,exception-safe functions(异常安全函数)不会泄露资源,也不允许数据结构被破坏。这样的函数提供 basic(基本)的,strong(强力)的,或者 nothrow(不抛出)保证。
  • strong guarantee(强力保证)经常可以通过 copy-and-swap 被实现,但是 strong guarantee(强力保证)并非对所有函数都可行。
  • 一个函数通常能提供的保证不会强于他所调用的函数中最弱的保证。
Vue3 - 详细实现流程图编辑器可视化平台示例代码,打造前端web网页流程图在线编辑绘制操作展示,vue3自定义绘制流程图在页面上画流程图功能(提供各种操作按钮及节点控件,界面美观交互式流程图插件) vue3,绘制流程图,在线流程图制作,在vue3中实现流程图,vue3做一个制作流程图的功能,vue3流程图组件,vue3流程图,流程图绘制展示,vue3流程图可视化在线编辑源码,基于VUE3的在线绘制流程图工具,vue3+logicflow流程图,绘制动态流程图并自定义节点和线,vue使用logicFlow绘制流程图,生成各种流程图图形可连接点移除,基于Vue的流程图组件,工作流程图,流程编辑器,vue3流程组件,vue-flow绘制流程图,绘制流程图示例demo,vue3 logicflow流程图使用教 阅读详情

相关推荐

压电阀控制器设计-总章

本文章用于回顾记录本人制作压电阀控制器的关键技术,本人所制作压电阀控制器已经上市得到应用,特在此对一些关键技术进行回顾记录,欢迎从事压电控制及压电阀相关人员一起讨论

qq_42712859的博客 850

[翻译] Effective C++, 3rd Edition, Item 29: 争取 exception-safe code(异常安全代码)(上)

Item 29: 争取 exception-safe code(异常安全代码)作者:Scott Meyers译者:fatalerror99 (iTePubs Nirvana)发布:http://blog.youkuaiyun.com/fatalerror99/exception safety(异常安全)有点像 pregnancy(怀孕)……但是,请把这个想法先保留一会儿。我们还不能真

艺术编程 4851

UniApp实现多端支付功能完整实现指南

支付业务流程多端支付实现安全性保障异常处理最佳实践支付基础知识多端支付实现统一支付封装安全性处理异常处理最佳实践根据具体业务场景选择合适的支付方式注重安全性和用户体验做好异常处理和日志记录持续优化和改进支付流程关注我,持续分享前端项目开发经验!

weixin_63454527的博客 2129

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 254

描边盛开火玫瑰

python描边盛开火玫瑰 代码如下 import turtle as t t.setup(800,800) t.hideturtle() t.speed(11) t.penup() t.goto(50,-450) t.pensize(5) t.pencolor("black") t.seth(140) t.pendown() t.speed(10) t.circle(-300,60) t.fd(100) #jiaodu80 #1ye t.seth(10) t.fd(50) t.fillcolor("gr

weixin_50973371的博客 2066

Win10 IPv6 远程桌面连接(小米路由器)

背景 由于家里是移动网,且懒得向运营商要公网IPv4,所以想着用IPv6试一下。 家里的环境是这样的: 宽带用的是移动的,没有公网IPv4,但经过测试发现IPv6是公网的 上网通过光猫拨号,而不是通过路由器 路由器是小米AC2100 光猫更改为桥接模式 路由器必须要通过PPPoE拨号,如果是光猫拨号,那么要找对应的维护人员辅助你把光猫改成桥接模式。通过PPPoE拨号路由器就能够拿到公网的IPv6网段,也就可以给底下的其他服务分配公网IPv6了。而如果是光猫拨号,那么只有路由器可以拿到公网的IP。其他的设

timczm的博客 1万+

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 394

5.1 rocr-runtime libhsakmt event 机制全景—从 Doorbell 到 Signal

本文介绍了ROCm/KFD中的Event机制架构,主要解决GPU异步计算中的反馈问题。Event机制分为三层:硬件层触发事件,内核层管理事件通知,用户态接收处理。事件类型分为三大类:同步类事件(如SIGNAL、QUEUE_EVENT)用于任务完成通知,异常类事件(如MEMORY、HW_EXCEPTION)报告错误,拓扑类事件(如NODECHANGE)处理设备变更。该机制通过共享内存和中断实现高效通信,避免CPU轮询开销。后续文章将深入内核实现、Thunk层封装和多队列完成检测实战。

图形/计算GPU的驱动与虚拟化技术分享园地 1308

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 349

基于Python的智能动漫更新监控爬虫系统:从原理到实战

本文详细介绍了如何使用Python构建一个智能化的动漫更新监控系统,涵盖从基础爬虫到高级自动化监控的全套解决方案。文章将深入探讨最新的网络爬虫技术,包括异步请求、反反爬策略、智能解析算法等,并提供完整的代码实现和性能优化方案。通过本系统,用户可以实时获取多个动漫平台的更新信息,并通过多种渠道接收通知。关键词:Python爬虫、动漫监控、自动化通知、异步爬虫、反反爬策略。

2201_76125261的博客 902

后仿真中《SDF反标必懂连载篇》之 Negative Delay (理论篇)

今天,学习一下SDF文件中的负数延时问题。比如下面的图片中的SETUPHOLD 中的hold time 为负数。

那么菜的博客 2578

跟我学c++中级篇—类型定义的方式

本文系统阐述了编程中类型定义的重要性及c++中的实现方式。重点对比了宏定义、typedef与modern using三种机制,指出using在可读性、灵活性和模板支持上的优势,推荐优先使用。同时提醒开发者注意别名滥用、类型歧义、错误信息冗长等问题,强调统一规范与合理设计。最终结论:在现代c++开发中,应以using为主,兼顾简洁性与可维护性,避免陷入命名混乱与编译困境。

fpcc的专栏 88

CKA-CN考试之路----2

部分知道就可以了,考试不用输入,后面不再复述.这里我们需要将两端内容凭借成1个yaml。执行题目中给的的命令进行测试。

Q先生 243

【C++——类和对象(上)】筑基篇:以封装奠定面向对象之基

本文系统梳理C++类与对象的核心知识点,涵盖类的定义、访问限定符(public/private)、类作用域、对象实例化及内存布局。重点解析封装思想、this指针机制、空类大小为1字节的原因,以及类与对象在内存中的存储差异。通过对比C语言结构体与C++类,揭示面向对象设计的优势:数据保护、接口统一、命名隔离与代码可维护性提升。内容由浅入深,适合作为学习笔记与复习参考。

2602_95684935的博客 493

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 307

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 450

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

#SQLite 数据库笔记一:(概念 / 终端指令 / SQL 语句 / C 编程 / 回调函数)

数据库 = 数据的仓库,假设数据是海量。能力说明存储数据持久化保存,程序退出数据还在统计数据、分组、排序数据管理增、删、改、查(CRUD)嵌入式设备上也会用数据库(单片机网关、路由器、工业 HMI 都常见),因为 sqlite 体积小、无需服务进程、单文件即可。数据库 = 库 > 表 > 记录(行) > 字段(列)sqlite3 = 开源、C 写的、单文件、免安装、最多 2T 的嵌入式关系型数据库。

lwq20051110的博客 251

1-10 PostgreSQL中多表连接全解:inner/left/right/full/cross join

本文基于 PostgreSQL 15+ 完整拆解 5 种主流连接:内连接、左连接、右连接、全连接、笛卡尔连接。结合可落地的测试数据,演示每种连接的执行逻辑、返回结果,重点剖析 NULL 值对 JOIN 匹配的核心影响,彻底解决多表连接数据丢失、匹配异常、结果错乱的问题。

zhangjw34的专栏 105

【C++ 基础入门】C++继承(下):问题、底层原理与组合

C++继承(菱形继承的问题、虚继承原理、继承与组合、构造顺序)

2601_96587588的博客 802

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级 备考:莫队算法离线区间查询详解

备赛进行时:第十八届蓝桥杯软件赛报名已于 2026-10-08 10:00 开启、12-18 14:00 截止,省赛定在 2027-04-10(C/C++·Python 程序设计 9:00–13:00);中国电子学会 2026 年 12 月软件编程(C/C++ 一级至十级、Python 一级至六级)考级报名 9/23–12/2 16:00、统考 12-12;全国青少年信息素养大赛 2026—2027 赛季算法应用主题赛也已进入复盘练兵窗口。

IT_Scratch的博客 170

c++ explicit关键字详解

explicit。

拒绝AI文,努力在AI时代写出感动人心的好文章 151

825 mW 顶上 212 °C:ASM491S 的 105 °C/W、250 mA 短路电流与热关断

25 °C 环境里结温 34.1 °C,125 °C 环境里结温 134.1 °C——后者已经离 150 °C 只有 16 °C 了,但还在范围内。换个角度看同一件事:从 25 °C 和从 125 °C 起算,温升都是 86.6 °C,但一个到 111.6 °C(还在范围内),一个到 211.6 °C(远远超限)。这个算例里 20 °C/W 是我假设的,实际值取决于机壳材料、密封程度、有没有风扇,跨度可以从 5 °C/W 到 50 °C/W 以上。外面 50 °C 的机箱,盒内就是 63.9 °C。

ANSILIC的博客 393

《C++ 项目:手写大模型接入 SDK》Gemini接口设计(上):初始化、全量响应实现和测试

本文系统完成了Gemini模型通过OpenAI兼容API的接入开发,基于Apifox验证接口规范,设计并实现GeminiProvider类,涵盖初始化、状态检测、全量响应等核心功能。重点处理了代理配置强制校验、Bearer鉴权、JSON序列化与响应解析等工程细节,并通过GTest与CMake完成单元测试,验证链路可用性。项目遵循策略模式,保持与DeepSeek、ChatGPT等模型的统一抽象接口,显著提升SDK可扩展性。

2501_91275995的博客 398

【容斥原理计数】题解:P9489 ZHY的表示法_容斥原理_计数问题_前缀和问题转化_算法竞赛C++

给定 $ n $ 个正整数,求区间 $[l,r]$ 中有多少正整数 $ x $ 可表示为 $ \sum \lfloor \frac{y}{x_i} \rfloor $。通过二分确定最大 $ y $,转化为求 $ 1 \sim H $ 中是某些 $ x_i $ 倍数的数的个数,利用容斥原理与 DFS 枚举子集计算,结合 LCM 预判剪枝及去重优化,时间复杂度 $ O(2^n \log V) $,可高效处理 $ r \le 10^9 $ 的数据。

YLCHUP的博客 106

头歌 数据结构——C&C++线性表实训

本系列任务围绕线性表的链式实现,依次完成顺序构建、逆序构建、排序插入、查找、删除、求长度及栈、队列、集合等应用。通过动态内存分配与指针操作,掌握单链表的基本操作与实际应用,涵盖插入、删除、遍历、搜索等核心功能,最终实现数据结构在典型场景中的灵活运用。

一名敲代码的苦13程序员 244

字母移动游戏题解:贪心求最长可保留连续子串(附 C 实现)

本题核心思想是:最少移动步数 = 总长度 - 可保留的最长连续子串长度。未移动字母在T中必须为连续子串,在S中需保持相对顺序(即子序列)。通过枚举T的所有连续子串起点,贪心匹配S中的对应字符,求出最长可保留长度L,答案即为n−L。时间复杂度O(n²),空间复杂度O(n),适用于n≤1000。

冻柠茶飞冰走茶 592
上一篇: [翻译] Effective C++, 3rd Edition, Item 29: 争取 exception-safe code(异常安全代码)(上)
下一篇: [翻译] Effective C++, 3rd Edition, Item 30: 了解 inlining(内联化)的来龙去脉(上)
fatalerror99
博客等级 码龄24年 166粉丝 20原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值