More Effective C++ 35个改善编程与设计的有效方法笔记与心得 3

三. 异常

条款9: 利用destructors避免泄露资源

‌‌‌‌  在编程中,"资源"可以指任何系统级的有限资源,如内存文件句柄网络套接字等。"泄露"则是指在应用程序中分配了资源,但在不再需要这些资源时没有正确地释放它们。这种状况常常会导致资源过度使用,如内存溢出,这个问题在长时间运行的程序中尤其严重。

‌‌‌‌  而"利用destructors避免泄露资源"则是一种建议。在C++中,析构函数(Destructor) 是一种特殊的成员函数,它会在每次删除所创建的对象时执行。

‌‌‌‌  匆忙地,可以在析构函数中清理和释放资源,这样当对象的生命周期结束时,析构函数会自动调用,阻止资源的泄漏。这是一种被称作RAII(Resource Acquisition is Initialization)的编程技术。

‌‌‌‌  举例来说,如果你的类负责管理一个动态分配的内存块,那你可以在析构函数中使用 delete 来释放这个内存块。这样只要对象存在,你就可以确保它持有的资源会在合适的时机被释放。

‌‌‌‌  所以,"利用destructors避免泄露资源"的意思是:在设计类时,应该充分利用构造函数和析构函数来管理资源,以防止任何可能的资源泄漏。

请记住
‌‌‌‌  只要坚持这个规则,把资源封装在对象内,通常便可以在exceptions出现时避免内存泄漏。

条款10: 在constructors内阻止资源泄露(resource leak)

‌‌‌‌  "在constructors内阻止资源泄露"这句话的意思是,在设计类的构造函数时需要以一种防止资源泄露(如内存泄露)的方式管理资源

‌‌‌‌  我们通常在构造函数中获取需要的资源,并在析构函数中释放这些资源,这是一种常用的RAII(资源获取即初始化) 技术。但是,如果在获取资源后和资源释放之间发生异常,可能会导致资源泄漏

‌‌‌‌  以内存申请为例,如果在构造函数中申请了内存,但后续程序出现异常,且这个异常没有被捕获,那么程序可能在没有执行析构函数(也就是没有释放内存)的情况下终止,从而导致内存泄露

‌‌‌‌  为了避免这种情况,你可以在构造函数中使用try-catch块来捕获异常,一旦抛出异常,就清理申请的资源,再重新抛出异常。或者利用一些智能指针(如unique_ptr、shared_ptr等),它们在析构时可以自动释放资源,可用来避免资源泄漏。这样做,就可以确保在构造函数执行过程中,无论是否发生异常,申请的资源都能被妥善处理,从而避免资源泄露。

条款11: 禁止异常(exceptions)流出destructors之外

‌‌‌‌  "禁止异常(exceptions)流出destructors之外"这句话的含义是:在编写析构函数(destructors)时,应确保任何可能抛出的异常都被妥善处理,不允许它们传播(或“流出”)到析构函数的外部。

‌‌‌‌  这是一个重要的编程原则,因为在析构函数中允许异常传播可能会引发各种复杂的问题。例如,如果析构函数在清理资源时抛出了一个异常,但是这个异常在外部没有被捕获,那么这个异常会导致程序猝死(即异常终止)。更糟糕的是,如果析构函数是在处理另一个异常时被调用的(比如在堆栈展开过程中),而它又抛出了第二个异常,那么程序会立即被终止。

‌‌‌‌  因此,良好的做法是使析构函数只做它该做的事情,即清理资源,而且要确保它能正确执行,不会抛出异常。在多数情况下,析构函数不应该进行可能会抛出异常的操作。如果无法避免,那么就应该在析构函数内部处理可能抛出的异常,确保它们不会流到析构函数的外部。这通常可以通过添加一个try-catch块来实现。

条款12: 了解“抛出一个exception”与“传递一个参数”或“调用一个虚函数”之间的差异

主要存在三个差异

  1. exception objects总是会被复制,如果以by value方式捕捉,他们甚至被复制两次。至于传递给函数参数的对象则不一定得复制。
  2. “被抛出成为exception”的对象,其被允许的类型转换动作,比“被传递到函数去”的对象少
  3. catch子句以其“出现于源代码顺序”被编译器检验对比,其中第一个匹配成功者便被执行;而当我们以某对象调用一个虚函数,被选中执行的是哪那个“与对象类型最佳吻合”的函数,不论它是不是源代码所列的第一个。

解释
‌‌‌‌  “抛出一个exception”,"传递一个参数"以及"调用一个虚函数"都是编程中常见的操作,不过它们在功能和目标上有着显著的差异。

‌‌‌‌  首先,"抛出一个exception"是一种特殊的程序流控制机制,用于处理程序运行时的异常情况。当程序运行到一个错误状态,无法正常执行时,就可以抛出(throw)一个异常。这将立即中断当前函数的执行,将控制权转移到最近的可以处理该异常的异常处理程序(catch语句)。异常是一种用于处理程序错误的强大工具,但是如果过度使用,可能导致代码逻辑难以理解和维护。

‌‌‌‌  其次,"传递一个参数"是调用函数时的一种操作。通过参数,我们可以将数据从函数调用者传递到被调用的函数中。参数可以是任何类型的数据,如整数、浮点数、字符串、对象等。

‌‌‌‌  最后,"调用一个虚函数"是面向对象编程中的一个概念,出现在如C++这样支持多态性(Polymorphism)的语言中。通过在基类中声明虚函数,派生类可以根据需要覆写(Override)这个函数。在运行时,通过基类指针或引用调用虚函数,会根据实际的对象类型来决定调用哪个版本的虚函数。这是实现运行时多态性的关键。

‌‌‌‌  总的来说,“抛出一个exception”通常用于处理错误,“传递一个参数”则是在函数之间传递数据,“调用一个虚函数”是实现多态性的手段。这三者都是编程中重要的概念,且在使用时目标和上下文有非常大的差异。

针对上面的三点主要差异,这里做一下解释说明

  1. 这里的表述涉及到了在C++中对异常(exception)的处理方式以及函数参数的传递方式。

‌‌‌‌  首先,在C++中当你抛出一个异常对象时,这个对象会被复制。系统从throw语句开始,搜索这个异常匹配的catch块,这个过程被称为异常传播。在这个过程中,原始异常对象会被拷贝一次。

‌‌‌‌  然后,当异常被catch块捕获时,如果以by value方式捕捉,即’catch (Exception e)',参数e是原始异常的副本,这样就会发生第二次复制。因此,通过by value的方式捕获异常将导致异常对象被复制两次。

‌‌‌‌  另一方面,函数参数的传递方式可以有两种,一种是by value,一种是by reference。当我们通过by value方式传递参数时,和catch块捕捉异常一样,参数也会被复制。但是,如果我们选择通过by reference形式传递,例如’void function(Exception& e)',则不会发生复制,参数e是原始异常对象的引用。

‌‌‌‌  所以这句话的意思是:异常对象会在传播过程中被复制,如果通过by value方式被捕获,还会发生一次复制。而函数参数是否复制则取决于参数的传递方式。如果参数是通过by value方式传递,就会复制,如果是通过by reference方式传递,就不会复制。

  1. 这段话的含义是,在C++编程中,当一个对象被抛出为一个异常(exception)时,它可以进行的类型转换比作为函数参数传递的对象要少。

‌‌‌‌  当一个对象被抛出为异常时,仅允许以下类型转换:

  1. 到基类的转换:如果抛出的是一个派生类对象,catch块可以捕获基类的异常。
  2. 通过构造函数定义的转换:如果存在一个构造函数,其唯一参数是异常的源类型或者源类型可以转换为这个参数类型。

‌‌‌‌  然而,当一个对象作为函数参数被传递时,除了上述的转换之外,还允许以下几种转换:

  1. 提升(Promotion):比如将整数字面量提升为整数、将字符提升为整数等。
  2. 标准类型转换:如将整数转为浮点数或者相反,将指针转为布尔值等。
  3. 用户定义的转换,例如通过转换函数进行类型转换。

‌‌‌‌  因此,“被抛出成为exception”的对象,其被允许的类型转换动作,比“被传递到函数去”的对象要少。

  1. 这段话对catch子句的处理方式和虚函数的调用进行了比较,并强调了两者在选择执行方式上的不同。

‌‌‌‌  catch子句以其"出现于源代码顺序"被编译器检验对比,其中第一个匹配成功者便被执行:
‌‌‌‌  这部分是对C++异常处理机制的描述。当一个异常被抛出时,编译器会按照catch子句在源代码中出现的顺序逐一检验这个异常是否匹配。一旦找到第一个匹配的catch子句,就会执行这个子句中的代码。这就解释了为什么我们需要从最详尽的异常类型开始写catch子句,再逐渐写出越来越一般的类型,因为一旦找到匹配的子句,就不会再继续查找。

‌‌‌‌  当我们以某对象调用一个虚函数,被选中执行的是哪那个“与对象类型最佳吻合”的函数,不论它是不是源代码所列的第一个: 这部分是在描绘多态性在面向对象编程中的应用。在C++里,当我们通过一个基类指针或引用来调用派生类的虚函数时,实际上执行的是派生类版本的函数,即使在源代码中,这个虚函数的声明可能并不是第一个出现的。这是因为在编译和运行时,编译器和运行环境会选择那个与对象实际类型最符的函数来执行,这种功能称为多态。

‌‌‌‌  通过对比,这段话强调了catch子句和虚函数在执行选择上的区别:catch子句按照源代码顺序选择,而虚函数则是根据对象的实际类型来决定。

条款13: 以by reference方式捕捉exception

请记住
‌‌‌‌  catch by reference,就可以避开对象删除问题;可以避开exception objects的切割问题;约束了exception objects需要被复制的次数

解释
‌‌‌‌  在C++中,我们可以用by reference方式来捕捉异常。这就意味着我们会直接使用到原本抛出的异常对象,而不是它的副本。

‌‌‌‌  C++里的异常处理机制中,当我们使用“catch(异常类型 & 引用)” 的方式来捕获异常,这就是以by reference方式捕捉异常。按照这种方式,系统只需要生成一次异常对象副本,即在抛出异常时,不需要在 catch 子句中再复制一次。这对于大对象或者复制对象资源消耗大的情况下,是很有用的策略。

try {// 代码块,可能抛出异常throw MyException();
}
catch (MyException& e) {// 这里捕获的 e 是一个引用, 会直接影响到原本的异常对象// 对 e 的改变也会影响到原先抛出的 MyException 对象
}

‌‌‌‌  此外,采用这种方式捕捉异常还可以解决一些多态问题。如果有一些异常类通过继承关系链接,捕捉父类异常引用可以接到子类的异常。这是由于引用可以保持多态性,允许我们通过基类引用来访问派生类中的重写虚函数。而拷贝的方式会切除对象的派生部分,带来切割问题(slicing problem)。

‌‌‌‌  “切割问题”(slicing problem),源于C++对于对象的处理方式,特别是在处理对象副本和继承关系时常常遇到。

‌‌‌‌  切割问题通常发生在当你有一个派生类(derived class)对象,并且你试图将其拷贝到一个基类(base class)对象时。在这个过程中,派生类中的所有附加信息都会被“切割”掉,只剩下基类部分。这是因为基类只能容纳基类的成员,对应的派生类的成员对其来说是未知的,复制过程中会丢失这部分信息,这就是所谓的切割问题。

‌‌‌‌  当我们处理异常时,如果用基类类型去捕获派生类的异常(这在面向对象设计中是很常见的情况,我们希望用同一段处理代码来处理一类相关的异常),如果我们是按值捕获的,就会发生切割问题,所有的异常都会被处理成基类类型的异常。

‌‌‌‌  但如果我们使用引用方式捕获异常,就可以避免切割问题。因为引用方式不涉及对象的复制,它直接引用到了原始的异常对象,保持了对象的完整性,包括其真正的类型。这样基于类型的动态绑定仍然能够正确工作,我们能够捕获并处理正确的异常类型。

‌‌‌‌  所以,“catch by reference可以避开exception objects的切割问题”这句话的意思就是,通过引用的方式捕获异常可以避免丢失原始异常类型的信息,从而避免了异常切割的问题。

条款14: 明智运用exception sepcifications

‌‌‌‌  “明智运用exception specifications” 这句话关注的是如何适当地使用C++中的异常规范(exception specifications)
‌‌‌‌  
‌‌‌‌  在C++中,异常规范是一种语法,允许函数说明它可能抛出的异常类型。这是在函数声明的尾部,通过"throw(异常类型列表)"来进行说明的。比如,一个函数可能这样声明:“void foo() throw(A, B);”,表示foo()函数可能会抛出类型A和B的异常。如果函数声明时未指定throw列表,或者这个列表为空,那么函数就可以抛出任何类型的异常,或不抛出异常。

‌‌‌‌  然而,在C++11以后,异常规范这一设定已经被废弃,不再建议使用。取而代之的是noexcept关键字,用来指示函数是否可能抛出异常。
‌‌‌‌  
‌‌‌‌  在"明智运用exception specifications"这个表述中,"明智"两字暗示了要根据具体情况和需求谨慎使用这类规范,避免滥用。过度使用异常规范可能引起不必要的复杂性,并可能对性能产生影响。另外,也需要注意随着C++标准的更新,异常规范的使用情况也在发生变化。所以,在设计和编写代码时,需要密切关注最新的实践和建议,明智地运用这些规范。

条款15: 了解异常处理(exception handing)的成本

解释
‌‌‌‌  "了解异常处理(exception handing)的成本"这句话是在说我们应该了解在我们的代码中使用异常处理机制带来的开销。

‌‌‌‌  在大多数编程语言中,异常处理都有自己的开销。这里的"开销"主要包括两部分:时间开销和空间开销。

  1. 时间开销:当一个异常被抛出时,需要在调用栈上回溯查找匹配的catch块,这个过程需要时间。另外,创建异常对象、复制异常对象到catch块(如果不是以引用方式捕获)以及析构对象都是需要时间的。

  2. 空间开销:异常处理机制需要在内存中保存足够的信息,以便在异常发生时可以正确地解 unwinding 调用栈。这包括异常对象本身的空间,以及可能需要通过栈回溯的信息。

‌‌‌‌  通常来说,如果异常不频繁发生,这些开销可能不大。但是在一些性能临界的应用中,这些成本可能就相当重要了。

‌‌‌‌  了解这些异常处理的成本,可以帮助我们更好地在性能和可读性、易用性之间做出平衡。例如,一般来说,我们推荐在异常是真正"异常"的情况下使用异常机制,比如函数预计不会失败,但却由于某些无法预计的原因比如内存耗尽而失败。对于预期可能会失败的函数,比如文件打开操作,使用返回错误码可能是更好的方式。这样可以保证性能,但又不牺牲代码的结构。

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

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

相关文章

Linux 安装 Redis 教程

优质博文:IT-BLOG-CN 一、准备工作 配置gcc:安装Redis前需要配置gcc: yum install gcc如果配置gcc出现依赖包问题,在安装时提示需要的依赖包版本和本地版本不一致,本地版本过高,出现如下问题&#xff1a…

Jupyter无法导入库,但能在终端导入的问题

Jupyter无法导入库,但能在终端导入 ❌错误问题描述:conda activate LLMs激活某个Conda的环境后,尽管已经通过conda或者pip在这个环境中安装了一些🐍Python的库,但无法在Jupyter中导入,却能在终端成功导入。…

京东商品详情数据接口(JD.item_get)丨京东API实时接口指南

京东商品详情API接口(JD.item_get)是京东开放平台提供的一个数据接口,用于获取京东平台上单个商品的详细信息。 通过这个接口,开发者可以获取到包括商品名称、品牌、产地、规格参数、价格信息、销量、评价、图片、描述等在内的详…

Node.js开发实战 视频教程 下载

ode.js开发实战 视频教程 下载 下载地址 https://download.csdn.net/download/m0_67912929/89487510 01-课程介绍.mp4 02-内容综述.mp4 03-Node.js是什么? .mp4 04-Node.js可以用来做什么?.mp4 05-课程实战项目介绍.mp4 06-什么是技术预研? .mp4 07-Node.js开发环境…

Windows 11 安装 安卓子系统 (WSA)

How to Install Windows Subsystem for Android (WSA) on Windows 11 新手教程:如何安装Windows 11 安卓子系统 说明 Windows Subsystem for Android 或 WSA 是由 Hyper-V 提供支持的虚拟机,可在 Windows 11 操作系统上运行 Android 应用程序。虽然它需…

【JS】注意考点

1.声明变量时所遵循的规则: (1)可以使用一个保留关键字var同时声明多个变量 (2)可以在声明变量的同时对其赋值, (3)如果只是声明了变量,并未对其赋值,其值就默认为 Undefined。 (4)保留关键字var可以用作for语句和for…in语句…

python基础_类

在Python中,类(Class)是面向对象编程(OOP)的核心概念之一。类提供了一种创建新对象的模板,这些对象通常被称为类的实例或对象。以下是关于Python类的一些关键点和特性: 定义类 类通过class关键…

PostgreSQL的系统视图pg_stat_wal

PostgreSQL的系统视图pg_stat_wal 在 PostgreSQL 数据库中,pg_stat_wal 视图提供了与 WAL(Write-Ahead Logging)日志有关的统计信息。WAL 是 PostgreSQL 用于确保数据一致性和持久性的重要机制。因此,监控和分析 WAL 活动对于数据…

ctfshow-web入门-命令执行(web71-web74)

目录 1、web71 2、web72 3、web73 4、web74 1、web71 像上一题那样扫描但是输出全是问号 查看提示:我们可以结合 exit() 函数执行php代码让后面的匹配缓冲区不执行直接退出。 payload: cvar_export(scandir(/));exit(); 同理读取 flag.txt cinclud…

文华财经博易大师盘立方多空波段止损画线指标公式

TT:PERIOD7; EMA120:EMA(C,120); RSV:(CLOSE-LLV(LOW,9))/(HHV(HIGH,9)-LLV(LOW,9))*100; K:SMA(RSV,3,1); D:SMA(K,3,1); J:3*K-2*D; DRAWTEXT(TT&&J<0,L,多),VALIGN0; DRAWTEXT(TT&&J>100,H,空),VALIGN2; IF(TT,EMA(C,60),NULL),RGB(255,255,2…

JavaScript数组对象 , 正则对象 , String对象以及自定义对象介绍

1. Array数组对象 数组对象是使用单独的变量名来存储一系列的值。 1.1创建一个数组 创建一个数组&#xff0c;有三种方法。 【1】常规方式: let 数组名 new Array();【2】简洁方式: 推荐使用 let 数组名 new Array(数值1,数值2,...);【3】字面:在js中创建数组使用中括号…

【ubuntu 】使用samba配置共享用户home目录和其他具体路径

目录 1 安装samba 2 修改Samba配置文件 3 增加Rose用户的samba帐号 4 重启samba 5 测试 1 安装samba 使用如下命令安装samba&#xff1a; sudo apt-get updatesudo apt-get install samba openssh-server 2 修改Samba配置文件 sudo cp /etc/samba/smb.conf /etc/samba…

试用笔记之-收钱吧安卓版演示源代码,收钱吧手机版感受

首先下载&#xff1a; https://download.csdn.net/download/tjsoft/89499105 安卓手机安装 如果有收钱吧帐号输入收钱吧帐号和密码。 如果没有收钱吧帐号点我的注册 登录收钱吧帐号后就可以把手机当成收钱吧POS机用了&#xff0c;还可以扫客服的付款码哦 源代码技术交流QQ:42…

Docker安装MySQL5

Docker安装MySQL5 前言 MySQL 是一个开源的关系型数据库管理系统&#xff0c;广泛用于各种 Web 应用程序的开发和生产环境中。MySQL 5 是 MySQL 数据库的一个较早版本&#xff0c;虽然不再是最新版本&#xff0c;但仍然被一些项目所使用和支持。 在 Docker 中安装 MySQL 5 可…

Docker 手册

帮助命令 docker 命令 --help镜像命令 docker images (-a所有 &#xff5c; -q只显示容器的ID) docker search 镜像名 docker pull 镜像名&#xff1a;版本号 docker rmi -f ID&#xff5c;镜像名&#xff1a;版本号 // 删除本地一个或多个镜像 docker rmi -f $(docker …

U盘数据恢复实战指南:原因、方案与预防措施

一、引言&#xff1a;U盘数据恢复概述 在数字化时代&#xff0c;U盘作为一种便携式存储设备&#xff0c;广泛应用于个人和企业中。然而&#xff0c;由于各种原因&#xff0c;U盘数据丢失的问题时有发生。U盘数据恢复技术便是在这种情况下应运而生&#xff0c;它帮助用户在数据…

TPS61085非同步650kHz,1.2MHz, 18.5V升压DCDC芯片

1 特点 TPS61085外观和丝印PMKI 2.3 V 至 6 V 输入电压范围 具有 2.0A 开关电流的 18.5V 升压转换器 650kHz/1.2MHz 可选开关频率 可调软启动 热关断 欠压闭锁 8引脚VSSOP封装 8引脚TSSOP封装 2 应用 手持设备 GPS接收器 数码相机 便携式应用 DSL调制解调器 PCMCIA卡 TFT LCD…

MySQL之事务特性ACID、并发事务问题、隔离级别

1、事务特性ACID 原子性&#xff1a;事务是不可分割的最小操作单元&#xff0c;要么全部成功&#xff0c;要么全部失败。一致性&#xff1a;事务完成时&#xff0c;必须使所有数据都保持一致状态。隔离性&#xff1a;数据库系统提供的隔离机制&#xff0c;保证事务在不外部并发…

Java并发编程基础知识点

目录 Java并发编程基础知识点1、线程&#xff0c;进程概念及二者的关系进程相关概念线程相关概念进程与线程的关系补充小知识点&#xff1a; 2、线程的状态Java线程的状态&#xff1a;Java线程不同状态之间的切换图示 3、Java程序中如何创建线程&#xff1f;①、继承Thread类②…

2024年华为OD机试真题-传递悄悄话 -C++-OD统一考试(C卷D卷)

2024年OD统一考试(D卷)完整题库:华为OD机试2024年最新题库(Python、JAVA、C++合集) 题目描述: 给定一个二叉树,每个节点上站着一个人,节点数字表示父节点到该节点传递悄悄话需要花费的时间。 初始时,根节点所在位置的人有一个悄悄话想要传递给其他人,求二叉树所有节…