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.mfbz.cn/a/761090.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式Linux系统编程 — 5.4 rand、srand 函数产生随机数

目录 1 随机数与伪随机数 2 rand和srand函数 3 示例程序 1 随机数与伪随机数 随机数在编程有广泛的应用场景,以下是一些常见的例子: 游戏开发:在电子游戏中,随机数用来决定各种事件的结果,如怪物的出现位置、敌人…

大会员体系是如何让积分流动起来,实现跨业态引流的?

积分,是大会员体系建设的重点,也是难点之一。 假设积分跨业态或者跨品牌流通,但没有一套统一的积分结算体系,就容易出现各业态或品牌利益分配不均的问题。 因此,大会员体系能有效落地的重点之一:集团必须…

【Week-G1】调用官方GAN实现MNIST数字识别,Pytorch框架

文章目录 1. 准备数据1.1 配置超参数1.2 下载数据1.3 配置数据 2. 创建模型2.1 定义鉴别器2.2 定义生成器 3. 训练模型3.1 创建实例3.2 开始训练3.3 保存模型 4. 什么是GAN(对抗生成网络)? 🍨 本文为🔗365天深度学习训练营 中的学…

千益畅行,旅游卡,如何赚钱?

​ 赚钱这件事情,只有自己努力执行才会有结果。生活中没有幸运二字,每个光鲜亮丽的背后,都是不为人知的付出! #旅游卡服务#

2024推荐整理几个磁力导航网站可提供海量资源的

都2024现在网上找资源像流水得鱼一样,抓一大把结果很难吃,我通宵特意整理的网站,网上有许多磁力导航网站可以提供海量的磁力链接资源,以下是一些有效的磁力导航网站推荐: 磁力搜索 链接: 资源类型&#x…

并发控制-事务的调度、数据不一致问题(更新丢失、脏读、不可重复读)、非串行调度的的可串行化

一、引言 1、数据库管理系统DBMS的事务处理技术实现的另一个主要功能部分是并发控制机制。并发控制机制完成的功能就是对并发执行的事务进行控制,保证事务的隔离性,从而进一步保持数据库的一致性。 2、事务的并发控制就是对并发执行的不同事务中的数据…

SpringMvc 执行原理

当用户请求 会发送到前端控制器,DisptcherServlet根据请求参数生成代理请求,找到对应的实际控制器,控制器处理请求,创建数据模型,访问数据库,将模型响应给中心控制器,控制器使用模型与视图渲染视…

前沿重器[52] | 聊聊搜索系统5:召回:检索、粗排、多路召回

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享,从中抽取关键精华的部分和大家分享,和大家一起把握前沿技术。具体介绍:仓颉专项:飞机大炮我都会,利器心法我还有。(算起来,专项启动已经…

KVM性能优化之CPU优化

1、查看kvm虚拟机vCPU的QEMU线程 ps -eLo ruser,pid,ppid,lwp,psr,args |awk /^qemu/{print $1,$2,$3,$4,$5,$6,$8} 注:vcpu是不同的线程,而不同的线程是跑在不同的cpu上,一般情况,虚拟机在运行时自身会点用3个cpus,为保证生产环…

EtherCAT笔记(五)—— 寻址方式与应用层协议

目录 1. EtherCAT 报文寻址 1.1 EtherCAT 网段寻址 1.1.1 直连模式 1.1.2 开放模式 1.2 段内寻址 —— 设备寻址 1.2.1 顺序寻址 1.2.2 设置寻址 1.3 逻辑寻址 1.4 关于WKC 2. 应用层协议 2.1 CoE : CANopen over EtherCAT 2.2 SoE (Servo Drive Profile …

现在电气真的比不过计算机吗 ?

电气工程和计算机科学在今天的科技和工业领域中各有其重要性和发展空间,并不存在简单的比较谁“比不过”谁的情况。我收集制作一份plc学习包,对于新手而言简直不要太棒,里面包括了新手各个时期的学习方向,包括了编程教学&#xff…

小白学python(第四天)顺序与分支篇

这几天因为个人原因,python篇会更新比较慢,还望大家谅解,那么废话不多说,我们现在就进入正题 顺序篇 这个没啥好说的,就是自上而下,依次执行 分支篇 条件(if)语句语法格式&#…

帝国CMS(EmpireCMS)漏洞复现

简介 《帝国网站管理系统》英文译为Empire CMS,简称Ecms,它是基于B/S结构,且功能强大而帝国CMS-logo易用的网站管理系统。 帝国CMS官网:http://www.phome.net/ 参考相关漏洞分析文章,加上更详细的渗透测试过程。 参考…

python自动化内存管理

引用 在编程中,引用是指用来标识、访问或操作某个对象的值的标识符或变量。我们可以将引用看作是对象的别名,通过引用可以操作对象,包括读取、修改和传递对象的值。 举例来说,假设我们有一个字符串对象name,我们可以创…

Vue中的axios深度探索:从基础安装到高级功能应用的全面指南

文章目录 前言一、axios 请求1. axios的概念2. axios的安装3. axiso请求方式介绍4. axios请求本地数据5. axios跨域6. axios全局注册7. axios支持的请求类型1)get请求2)post请求3)put请求4)patch请求5)delete请求 二、…

仓颉编程语言 -- 初识(二)

4、卓越性能 仓颉语言通过值类型、多层级静态分析优化和超轻量运行时,在计算机语言基准测试Benchmarks Game上,相比业界同类语言取得了较为明显的性能优势。 4.1 静态编译优化 仓颉编译采用模块化编译,编译流程间通过IR作为载体&#xff…

BCFtools安装

记得之前安装这个软件的时候是非常简单的,但是今天重新安装的时候出现了很多的麻烦,想想还是做个记录吧! bcftools的下载地址如下: Releases samtools/bcftools (github.com)https://github.com/samtools/bcftools/releases/这里我们选用的…

protobufjs解析proto消息出错RangeError: index out of range: 2499 + 10 > 2499解决办法

使用websocket通讯传输protobuf消息的时候,decode的时候出错了: RangeError: index out of range: 2499 10 > 2499 Error: invalid wire type 4 at offset 1986 出现这种错误的时候,99%是因为proto里面的消息类型和服务端发送的消息类型不…

AI绘画:提升效率的艺术之道

前言 AI绘画:提升效率的艺术之道 在当今数字时代,人工智能(AI)正以惊人的速度融入我们的生活各个领域。艺术界也不例外。AI绘画作为一种创新的工具和技术,正在改变着艺术家们的创作方式,并为他们带来了从来…

【多媒体】Java实现MP4和MP3音视频播放器【JavaFX】【音视频播放】

在Java中播放音视频可以使用多种方案,最常见的是通过Swing组件JFrame和JLabel来嵌入JMF(Java Media Framework)或Xuggler。不过,JMF已经不再被推荐使用,而Xuggler是基于DirectX的,不适用于跨平台。而且上述方案都需要使用第三方库…