《Effective C++》《构造/析构/赋值运算——8、别让异常逃离析构函数》

文章目录

  • 1、Terms 8:Prevent exceptions from leaving destructors
  • 2、面试相关
    • 2.1. 析构函数是否可以抛出异常?为什么?
    • 2.2. 如果析构函数抛出异常,会有什么后果?
    • 2.3. 如何避免析构函数抛出异常?
    • 2.4. 构造函数和析构函数在异常处理中的角色是什么?
    • 2.5. 如何确保析构函数的安全性?
  • 3、总结
  • 4、参考

1、Terms 8:Prevent exceptions from leaving destructors

C++ 并不禁止析构函数吐出异常,但它不鼓励你这样做。这是有理由的。考虑以下代码:

class Widget {
public:...~Widget() { ... } //假设这个析构函数可能会抛出异常
};int main() {std::vector<Widget> v;...return 0;
}//v在这里自动销毁

当 vector v 被销毁,他有责任销毁其内含的所有 Widgets。假设 v 内有10个Widgets,那么在程序结束时会逐个释放这10个Widget对象。但是假设在释放第1个对象时,第1个Widget的析构函数中抛出了异常,并且没有对任何异常进行任何处理,此时程序就会中断。
如果你的析构函数必须执行一个动作,而该动作可能会在失败时抛出异常,该怎么办?我们通过一个例子进行说明:
先建立一个类负责连接数据库:

class DBConnection {
public://该函数返回一个DBConnection对象static DBConnection create();//关闭数据库连接(失败会抛出异常)void close();
};

再建立另一个用来管理数据库对象:
下面通过数据库连接资源的类,并在其析构函数中调用 close。

//用来管理DBConnection对象
class DBConn {
public:...//确保数据库连接总是会被关闭~DBConn() { db.close(); }
private:DBConnection db;
};

这样就便于客户写出这样的代码:

int main() {//建立一个DBConnection对象并交给DBConn管理,使用DBConn的接口管理DBConnectionDBConn dbc(DBConnection::create());...return 0;
}//程序结束时,DBConn对象被销毁,因此会自动为DBConnection对象调用close

如果调用close()调用成功的话那么就一切都好。如果close()函数调用出错(有异常),那么DBConn析构函数也会传播该异常,导致程序出错。
有两个方法可以解决这个问题:

  • 方法1:如果close函数抛出异常,就结束程序,可以通过调用abort完成
DBConn::~DBConn()
{try { db.close(); }catch (...) {//制作运作记录,记下对close的调用失败std::abort();}
}

如果程序在析构期间发生一个错误,那么“强迫程序结束”是一个合理的设置。

  • 方法2:忽略(吞掉)这个异常
DBConn::~DBConn() {try { db.close(); }catch (...) {/*此处还可以做一个记录,记下对close的调用失败,其他什么都不做*/}
}

一般而言,忽略这个异常是个坏主意,因为忽略这个异常会造成不明确的行为和前面两个方法相比,还有一个更好的解决办法:
一般而言,忽略这个异常是个坏主意,因为忽略这个异常会造成不明确的行为一个更好的策略是重新设计DBConn接口,DBConn可以追踪所管理的DBConnection是否已经关闭:

  • 如果已经关闭就不做任何事情。
  • 如果还没关闭,并且抛出了异常,那么还是要使用到上面的两种解决方案。
class DBConn {
public:...DBConn::~DBConn(){if (!closed) {try { db.close(); }catch () {//此处还可以做一个记录,记下对close的调用失败}}} void close() { // 供使客户使用的新函数db.close();closed = true;}
private:DBConnection db;bool closed;
};

提供一个完整可以编译的代码:

#include <iostream>  // 假设有一个DBConnection类,这里仅声明,具体实现需要你自己提供  
class DBConnection {  
public:  void close() {  // 关闭数据库连接的逻辑  std::cout << "Closing database connection." << std::endl;  }  
};  class DBConn {  
public:  // 构造函数初始化db和closed  DBConn() : db(), closed(false) {  // 初始化数据库连接  std::cout << "Initializing database connection." << std::endl;  }  // 析构函数确保在对象销毁时关闭数据库连接  ~DBConn() { if(!closed){try{db.close();}catch(const std::exception& e){// 捕获异常并记录错误  std::cerr << "Error closing database connection: " << e.what() << std::endl; // 这里可以选择抛出异常或记录错误并继续}}}  // 提供一个公共的close方法供客户使用  void close() {  db.close();closed = true;}  private:  DBConnection db;  bool closed;  
};  int main() {  DBConn conn;  // 使用数据库连接...  // 显式关闭数据库连接  conn.close();  // 当conn对象离开作用域时,析构函数会自动关闭数据库连接  return 0;  
}

总结:

  • 析构函数绝对不要抛出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获并处理该异常。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数(而非在析构函数中)执行该操作。

2、面试相关

关于析构函数能否抛出异常,以下是一些相关的高频面试题:

2.1. 析构函数是否可以抛出异常?为什么?

  • 答案:析构函数不应该抛出异常。因为析构函数在对象生命周期结束时自动调用,如果它抛出异常,那么异常处理机制会变得复杂且难以管理。特别是当析构函数在异常处理过程中被调用时,如果它再次抛出异常,可能会导致程序崩溃。

2.2. 如果析构函数抛出异常,会有什么后果?

  • 答案:如果析构函数抛出异常,可能会导致资源泄露和其他未定义的行为。因为在析构函数中抛出的异常可能会中断其他重要的清理工作,如释放内存或关闭文件句柄等。此外,如果析构函数在异常处理中被调用,再次抛出异常会导致程序调用std::terminate(),从而终止执行。

2.3. 如何避免析构函数抛出异常?

  • 答案:可以通过在析构函数内部使用try-catch块来捕获并处理可能发生的异常,确保析构函数不会向外抛出异常。这样可以保持析构函数的异常安全性,并防止程序崩溃。

2.4. 构造函数和析构函数在异常处理中的角色是什么?

  • 答案:构造函数用于初始化对象的状态和成员变量,而析构函数用于清理对象的资源和执行必要的收尾操作。在异常处理中,如果构造函数抛出异常,对象的创建将失败,析构函数不会被调用。然而,如果异常发生在对象的使用过程中,析构函数将被调用以释放资源。因此,析构函数必须确保即使在异常情况下也能安全地释放资源。

2.5. 如何确保析构函数的安全性?

  • 答案:为了确保析构函数的安全性,应该避免在析构函数中执行可能抛出异常的操作。如果确实需要执行这样的操作,应该使用try-catch块来捕获并处理异常,确保析构函数不会向外抛出异常。此外,析构函数应该尽量简单和直接,只执行必要的清理工作,避免引入复杂的逻辑或调用可能抛出异常的方法。

3、总结

天堂有路你不走,地狱无门你自来

4、参考

4.1《Effective C++》

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【linux】lsof命令使用

1. 功能 lsof list open files, 列出被进程所使用的文件名称。 2. 基础语法 3. 参数含义 参数含义-a过滤出多个选项要同时满足的文件-U仅列出UNIX-like系统的socket文件类型。-u指定用户&#xff0c;比如-u atiaisi&#xff0c;会把用户atiaisi相关的进程使用的文件列出来。…

线程池小项目【Linux C/C++】(踩坑分享)

目录 前提知识&#xff1a; 一&#xff0c;线程池意义 二&#xff0c;实现流程 阶段一&#xff0c;搭建基本框架 1. 利用linux第三方库&#xff0c;将pthread_creat线程接口封装 2. 实现基本主类ThreadPool基本结构 阶段二&#xff0c;完善多线程安全 1. 日志信息打印…

【JavaScript】函数 ⑥ ( 使用 arguments 获取所有实参 | arguments 内置对象 | 伪数组概念 )

文章目录 一、使用 arguments 获取所有实参1、arguments 内置对象2、伪数组概念3、arguments 实参遍历4、arguments 代码示例 - 基本使用5、arguments 代码示例 - 遍历实参 一、使用 arguments 获取所有实参 1、arguments 内置对象 在 定义 JavaScript 函数 时 , 有时 不确定 形…

工业设备远程控制

随着科技的飞速发展和工业4.0的深入实施&#xff0c;远程控制技术在工业领域的应用变得日益重要。HiWoo Box网关作为连接工业设备与远程控制中心的桥梁&#xff0c;凭借其卓越的性能和稳定性&#xff0c;为工业设备远程控制提供了强大的支持。 一、工业设备远程控制的意义 工…

Rust所有权和Move关键字使用和含义讲解,以及Arc和Mutex使用

Rust 所有权规则 一个值只能被一个变量所拥有&#xff0c;这个变量被称为所有者。 一个值同一时刻只能有一个所有者&#xff0c;也就是说不能有两个变量拥有相同的值。所以对应变量赋值、参数传递、函数返回等行为&#xff0c;旧的所有者会把值的所有权转移给新的所有者&#…

后疫情时代CS保研沉思录暨2023年个人保研经验贴

个人情况 正如古话所说&#xff0c;最适合你的才是最好的。因此这里先贴上个人基本情况&#xff0c;用作参考。 如果你的个人情况与我相近&#xff0c;则有更强的参考作用。如果情况相差较大&#xff0c;也可以姑且引为例子来研究。 学校层次&#xff1a;中流至末流211 专业…

R语言中的常用数据结构

目录 R对象的基本类型 R对象的属性 R的数据结构 向量 矩阵 数组 列表 因子 缺失值NA 数据框 R的数据结构总结 R语言可以进行探索性数据分析&#xff0c;统计推断&#xff0c;回归分析&#xff0c;机器学习&#xff0c;数据产品开发 R对象的基本类型 R语言对象有五…

EKS-1.26 创建ingress-nginx绑定elb暴露服务

1. 创建集群 &#xff08;跳过不介绍&#xff09; 2. 创建Ingress-Nginx服务 部署项目地址【点我跳转】 推荐自定义部署 可绑定acm证书什么的自己属性 对应集群版本推荐阵列 https://github.com/kubernetes/ingress-nginx 修改下面的下载文件版本 Download the deploy.yaml…

Xxxxxx

数据库 1&#xff0c;B树与B树区别 1&#xff0c;B树每个节点存ID与其他数据字段&#xff0c;B非叶子结点&#xff0c;只存ID&#xff0c;叶子结点存完整数据 好处&#xff1a;每个层级B树&#xff0c;可以存储更多的额数据&#xff0c;层级更少&#xff0c;更扁平&#xff…

视觉大模型--deter的深入理解

但对于transformer用于目标检测领域的开创性模型&#xff0c;该模型言简意赅&#xff0c;但是但从论文理解&#xff0c;有很多细节都不清楚&#xff0c;尤其是解码器的query和二分图匹配(Bipartite Matching)和匈牙利算法(Hungarian Algorithm)相关&#xff0c;本文将根据代码详…

GLP-1药物固相合成法-载体树脂及层析填料

摘要&#xff1a;在生物医药GLP-1药物制备领域不仅可提供高稳定性载体树脂&#xff0c;还可根据客户需求&#xff0c;合成定制化载体&#xff08;如预接氨基酸固相合成载体、特殊溶胀度或基团负载量的载体、负载特殊基团的载体、清除树脂等&#xff09;。同时&#xff0c;海普专…

使用TCP协议就一定零丢包了吗?

简述数据包发送流程 为了简化模型&#xff0c;我们把中间的服务器给省略掉&#xff0c;假设这是个端到端的通信。且为了保证消息的可靠性&#xff0c;它们之间用的是TCP协议进行通信。 为了发送数据包&#xff0c;两端首先会通过三次握手&#xff0c;建立TCP连接。 一个数据包&…

Grafana+Promethues配置RocketMQ监控

背景 接前文&#xff0c;Promethues已经配置完毕&#xff0c;下面通过导入的Grafana的面板来配置RocketMQ监控页面 Dashboard 这里我们直接使用Grafana现成的面板配置 node_exporter&#xff1a;https://grafana.com/grafana/dashboards/1860 rocketmq_exporter的dashboar…

【蓝桥杯-枚举模板题】

蓝桥杯-枚举模板题 滑雪课程设计 P3650新的家乡 P8587枚举子集 B3622Air Cownditioning P9011 滑雪课程设计 P3650 核心的思路是把数据规定在[i,i17]里&#xff0c;不够的补&#xff0c;过大的减。枚举i以求最少的钱。 #include<bits/stdc.h> using namespace std;int n…

C#实现只保存2天的日志文件

文章目录 业务需求代码运行效果 欢迎讨论&#xff01; 业务需求 在生产环境中&#xff0c;控制台窗口不便展示出来。 为了在生产环境中&#xff0c;完整记录控制台应用的输出&#xff0c;选择将其输出到文件中。 但是&#xff0c;存储所有输出的话会占用很多空间&#xff0c;…

Chrome 设置在新窗口中打开链接(已登录google账号版)

Chrome的链接默认是在原标签页中打开的&#xff0c;如果要在新窗口中打开&#xff0c;需要自己自行设置&#xff0c;在此&#xff0c;针对已经登录google账号的chrome浏览器怎么进行设置进行说明。 一、点击登录图标->更多设置 二、选择其他设置->在新窗口中打开搜索结果…

15、Scalable Diffusion Models with Transformers

简介 官网 DiT&#xff08;Diffusuion Transformer&#xff09;将扩散模型的 UNet backbone 换成 Transformer&#xff0c;并且发现通过增加 Transformer 的深度/宽度或增加输入令牌数量&#xff0c;具有较高 Gflops 的 DiT 始终具有较低的 FID&#xff08;~2.27&#xff09;…

go库x/text缺陷报告CVE-2022-32149的处理方案

#问题描述 go库 golang.org/x/text &#xff0c;注意这里不是go的源码&#xff0c; 在0.3.8版本之前存在一个缺陷(Vulnerability) 缺陷ID CVE-2022-32149 具体描述 攻击者可以通过制作一个Accept-Language报头来导致拒绝服务。 具体的原因是&#xff0c;在解析这个Accept-L…

算法学习 | day34/60 不同路径/不同路径II

一、题目打卡 1.1 不同路径 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 拿到手&#xff0c;首先见到答案需要求的是种类的个数&#xff0c;并且看题目&#xff0c;每次移动的时候只有两个方向&#xff0c;这也就说明&#xff0c;对于某一个位置来说&#x…

深入剖析主机安全中的零信任机制及其实施原理

引言 在数字化转型加速与云端服务普及的大背景下&#xff0c;传统依赖边界的网络安全模式逐渐显露出其局限性。面对愈发复杂多变的威胁环境&#xff0c;零信任安全架构作为新一代的安全范式应运而生&#xff0c;尤其是在主机层面的安全实践中&#xff0c;零信任机制正扮演着至…