[翻译] Effective C++, 3rd Edition, Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)

基于OSPF技术的某企业网络设计(完整文档+ENSP拓扑图) 主要对网络规划与设计,以企业网络为背景,对某个中型企业网络进行规划设计,该企业总部在南京,在常州有一个分公司,需要实现总部和分公司能够访问外网,能够让分公司内的用户或主机访问总部服务器的资源,并且保证数据的安全性。在这次总部企业网络设计中,考虑到资金的问题,核心层部署2台核心三层交换机,负责转发总部内部用户的流量,并充当内部用户网关,能够实现内部不同网段的用户之间的连通性。对于一些对网络比较敏感的企业或用户,对网络的要求较高,对于网络的稳定性和安全性也有较高的要求。 阅读详情

Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.youkuaiyun.com/fatalerror99/

C++ 并不禁止从 destructors(析构函数)中引发 exceptions(异常),但是它坚决地阻止这样的实践。至于有什么好的理由,考虑:

class Widget {
public:
  ...
  ~Widget() { ... }              // assume this might emit an exception
};

void doSomething()
{
  std::vector<Widget> v;
  ...
}                                // v is automatically destroyed here

当 vector v 被销毁时,它有责任销毁它包含的所有 Widgets。假设 v 中有十个 Widgets,在第一个的析构过程中,抛出一个 exception(异常)。其它 9 个 Widgets 仍然必须被销毁(否则它们持有的所有资源将被泄漏),所以 v 应该调用它们的 destructors(析构函数)。但是假设在这个调用期间,第二个 Widget 的 destructors(析构函数)又抛出一个 exception(异常)。现在同时有两个活动的 exceptions(异常),对于 C++ 来说,这太多了。在非常巧合的条件下产生这样两个同时活动的异常,程序的执行会终止或者引发 undefined behavior(未定义行为)。在本例中,将引发 undefined behavior(未定义行为)。使用任何其它的标准库 container(容器)(比如,list,set),任何 TR1(参见 Item 54)中的 container(容器),甚至是一个 array(数组),都可能会引发同样的 undefined behavior(未定义行为)。也并非必须是 containers(容器)或 arrays(数组)才会陷入麻烦。程序过早终止或 undefined behavior(未定义行为)是 destructors(析构函数)引发 exceptions(异常)的结果,即使没有使用 containers(容器)和 arrays(数组)也会如此。C++ 不喜欢引发 exceptions(异常)的 destructors(析构函数)。

这比较容易理解,但是如果你的 destructor(析构函数)需要执行一个可能失败而抛出一个 exception(异常)的操作,该怎么办呢?例如,假设你与一个数据库连接类一起工作:

class DBConnection {
public:
  ...

  static DBConnection create();        // function to return
                                       // DBConnection objects; params
                                       // omitted for simplicity

  void close();                        // close connection; throw an
};                                     // exception if closing fails

为了确保客户不会忘记在 DBconnection objects(对象)上调用 close,一个合理的主意是为 DBConnection 建立一个 resource-managing class(资源管理类),在它的 destructor(析构函数)中调用 close。这样的 resource-managing classes(资源管理类)将在 Chapter 3(第三章)中一探究竟,但在这里,只要认为这样一个 class(类)的 destructor(析构函数)看起来像这样就足够了:

class DBConn {                          // class to manage DBConnection
public:                                 // objects
  ...
  ~DBConn()                             // make sure database connections
  {                                     // are always closed
   db.close();
  }
private:
  DBConnection db;
};

它允许客户像这样编程:

{                                       // open a block
   DBConn dbc(DBConnection::create());  // create DBConnection object
                                        // and turn it over to a DBConn
                                        // object to manage
 ...                                    // use the DBConnection object
                                        // via the DBConn interface
}                                       // at end of block, the DBConn
                                        // object is destroyed, thus
                                        // automatically calling close on
                                        // the DBConnection object

只要能成功地调用 close 就可以了,但是如果这个调用导致一个 exception(异常),DBConn 的 destructor(析构函数)将传播那个 exception(异常),也就是说,它将离开 destructor(析构函数)。这就产生了问题,因为 destructor(析构函数)抛出了一个烫手的山芋。

有两个主要的方法避免这个麻烦。DBConn 的 destructor(析构函数)能:

  • Terminate the program if close tHRows(如果 close 抛出异常就终止程序),一般是通过调用 abort:

DBConn::~DBConn()
{
 try { db.close(); }
 catch (...) {
   make log entry that the call to close failed;
   std::abort();
 }
}

如果在析构的过程遭遇到错误后程序不能继续运行,这就是一个合理的选择。它有一个好处是:如果允许从 destructor(析构函数)传播 exception(异常)可能会导致 undefined behavior(未定义行为),这样就能防止它发生。也就是说,调用 abort 就可以预先防止 undefined behavior(未定义行为)。

  • Swallow the exception arising from the call to close(抑制这个对 close 的调用造成的异常):

DBConn::~DBConn()
{
 try { db.close(); }
 catch (...) {
      make log entry that the call to close failed;
 }
}

通常,swallowing exceptions(抑制异常)是一个不好的主意,因为它会隐瞒重要的信息—— something failed(某事失败了)!然而,有些时候,swallowing exceptions(抑制异常)比冒程序过早终止或 undefined behavior(未定义行为)的风险更可取。程序必须能够在遭遇到一个错误并忽略之后还能继续可靠地运行,这才能成为一个可行的选择。

这些方法都不太吸引人。它们的问题首先在于程序无法对引起 close 抛出 exception(异常)的条件做出回应。

一个更好的策略是设计 DBConn 的 interface(接口),以使它的客户有机会对可能会发生的问题做出回应。例如,DBConn 能够自己提供一个 close 函数,从而给客户一个机会去处理从那个操作中发生的 exception(异常)。它还能保持对它的 DBConnection 是否已被 closed 的跟踪,如果没有就在 destructor(析构函数)中自己关闭它。这样可以防止连接被泄漏。如果在 DBConnection(原文如此,严重怀疑此处应为 DBConn ——译者注)的 destructor(析构函数)中对 close 的调用失败,无论如何,我们还可以再返回到终止或者抑制。

class DBConn {
public:
  ...

  void close()                                     // new function for
  {                                                // client use
    db.close();
    closed = true;
  }

  ~DBConn()
  {
   if (!closed) {
   try {                                            // close the connection
     db.close();                                    // if the client didn't
   }
   catch (...) {                                    // if closing fails,
     make log entry that call to close failed;      // note that and
     ...                                            // terminate or swallow
   }
  }

private:
  DBConnection db;
  bool closed;
};

将调用 close 的责任从 DBConn 的 destructor(析构函数)移交给 DBConn 的客户(同时在 DBConn 的 destructor(析构函数)中包含一个“候补”调用)可能会作为一种肆无忌惮地推卸责任的做法而使你吃惊。你甚至可以把它看作对 Item 18 中关于使 interfaces(接口)易于正确使用的建议的违背。实际上,这都不正确。如果一个操作可能失败而抛出一个 exception(异常),而且可能有必要处理这个 exception(异常),这个 exception(异常)就 has to come from some non-destructor function(必须来自非析构函数)。这是因为 destructor(析构函数)引发 exception(异常)是危险的,永远都要冒着程序过早终止或 undefined behavior(未定义行为)的风险。在本例中,让客户自己调用 close 并不是强加给他们的负担,而是给他们一个时机去应付错误,否则他们将没有机会做出回应。如果他们找不到可用到机会(或许因为他们相信不会有错误真的发生),他们可以忽略它,依靠 DBConn 的 destructor(析构函数)为他们调用 close。如果一个错误恰恰在那时发生——如果由 close 抛出——如果 DBConn 抑制了那个 exception(异常)或者终止了程序,他们将无处诉苦。毕竟,他们无处着手处理问题,他们将不再使用它。

Things to Remember

  • destructor(析构函数)应该永不引发 exceptions(异常)。如果 destructor(析构函数)调用了可能抛出异常的函数,destructor(析构函数)应该捕捉所有异常,然后抑制它们或者终止程序。
  • 如果 class(类)客户需要能对一个操作抛出的 exceptions(异常)做出回应,则那个 class(类)应该提供一个常规的函数(也就是说,non-destructor(非析构函数))来完成这个操作。
RK35X8调试YT8531单网口千兆以太网PHY芯片,修复ping不通或者IP地址丢失的问题 RK35X8上调试YT8531以太网PHY 阅读详情

相关推荐

实测对比:DeepSeek、豆包、千问三大AI模型生成测试用例谁更强?(附完整代码)

本文通过实测对比了DeepSeek、豆包、千问三大AI模型在生成测试用例方面的表现。文章提供了完整的Python代码实现,详细分析了各模型在用例完整性、准确性、边界覆盖及幻觉控制等维度的差异,并分享了工程化集成与多模型融合等优化策略,旨在帮助测试团队选择合适工具以提升工作效率。

ik6789012的博客 954

C# 析构函数解析(译)

C# 析构函数解析(译)   Chandra Hundigam 2002-06-18 原文地址: http://www.c-sharpcorner.com/UploadFile/chandrahundigam/UnderstandingDestructors11192005021208AM/UnderstandingDestructors.aspx   这篇文章谈了如何理解C#的析构

石头的专栏 2539

cst-python联合仿真的问题解决

cst与python联合仿真出现无法调用CST的问题解决

ayihu_的博客 2374

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

(先进后出)

xys_678的博客 254

在树莓派上部署训练好的YOLO模型

.pt.模型转换为onnx格式的模型,树莓派上部署模型

pai_da_xing8的博客 3887

Warning-[INFL_DELTA] Too many events zero delay loop

项目场景: 运用VCS工具执行仿真的时候,遇到如下错误提示,但仿真还在继续。 Warning-[INFL_DELTA] Too many events Possible zero delay loop. Too many events in the simulation at time 0 fs. 问题描述: 为什么会有这个问题; 什么导致了这个问题; 原因分析: 1、为什么会有这个问题 由于竞争条件或编码风格不明确,可能会产生零延时回路,导致仿真挂起或仿真时间无限。 2、什么导致了这个问题 举

hh199203的博客 1万+

C++移动构造函数详解

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

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

1104全部报表版本号-银监会报表

G4B_Ⅲ(a)X1《与非中央交易对手的衍生具交易对手信用风险违约风险计算表(现期风险暴露法)(第一档)》G4B_Ⅲ(b)X1《与非中央交易对手的证券融资交易对手信用风险违约风险计算表(权重法)(第一档)》G4B_Ⅲ(a1)X2《与非中央交易对手的衍生具交易对手信用风险违约风险计算表(标准法)(第二档)》Ⅲ(a)X2《与非中央交易对手的衍生具交易对手信用风险违约风险计算表(现期风险暴露法)(第二档)》Ⅲ(a1)X1《与非中央交易对手的衍生具交易对手信用风险违约风险计算表(标准法)(第一档)》

数字化老司机 6300

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

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

Maxwell_Newton的博客 349

VS2019手动添加bits/stdc++.h全指南(附解决‘无法打开源文件‘错误)

本文详细介绍了在Visual Studio 2019中手动配置C++万能头文件bits/stdc++.h的完整方法,解决常见的'无法打开源文件'错误。通过分析GCC与MSVC编译器生态差异,提供了纯手动创建和MinGW移植两种可靠方案,并给出详细的步骤、常见错误排查及最佳实践建议,帮助开发者高效配置竞赛开发环境。

weixin_29011395的博客 549

DirectX function “GetDeviceRemovedReason“ failed with DXGI_ERROR_DEVICE_HUNG (“The GPU will not resp

游戏报错"DXGI_ERROR_DEVICE_HUNG"的解决方案 在使用RTX 40系显卡运行FIFA 23等游戏时,可能出现DirectX报错导致游戏崩溃。这主要由于最新显卡驱动与DirectX 12的兼容性问题。解决方法包括: 降级显卡驱动版本 修改游戏配置文件,将DIRECTX_SELECT设为1 在显卡控制面板中关闭垂直同步、抗锯齿、DLSS 3等功能 调整电源模式为性能优先 这些操作可以有效解决因驱动兼容性导致的游戏崩溃问题。

猿小白的博客 3893

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

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

fpcc的专栏 88

【AutoSAR】【BootLoader】AutoSAR Bootloader 原理与流程

目录 一、HIS规范 二、BOOTLOADER软件架构 三、烧写流程 一、HIS规范 国外整车厂意识到在线刷新ECU的重要性,由奥迪公司、宝马公司、戴姆勒公司、保时捷公司、大众公司成立一个委员会,名为HIS委员会,专门负责帮助汽车制造商掌握微处理器控制单元软件和质量保证领域的原理和方法; HIS Bootloader的主要功能是通过标准的通信接口对ECU进行再编程 。 HIS Bootloader的软件设计基于AUTOSAR体系架构、ISO14229、ISO15765-2、HI...

专注汽车软件开发、AutoSAR、车载以太网、SOA、EE架构。 5250

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

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

2602_95684935的博客 500

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C++ 函数模版

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

2501_94041724的博客 307

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

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

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

Fuyo_0209的博客 450

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

#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 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
下一篇: [翻译] Effective C++, 3rd Edition, Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
fatalerror99
博客等级 码龄24年 166粉丝 20原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值