C++:异常处理

异常处理中对象参数与引用参数的选择

User:

  • catch()中的参数如果使用基类的引用,是不是相当于声明了一个引用,它是基类类型的,catchthrow的抛出的对象之后,就相当于这个基类引用指向了抛出的对象?

ChatGPT:

  • 是的,你说得很对。当在 catch 块中使用基类的引用作为参数时,相当于声明了一个引用,它是基类类型的。当程序抛出一个异常并且在调用栈中找到匹配的 catch 块时,如果异常的类型与 catch 块中指定的引用类型相匹配,那么这个引用会指向被抛出的异常对象
    这样做的好处是可以使用基类的引用来捕获派生类对象的异常,从而实现多态性。因为派生类对象也是基类对象,所以可以使用基类的引用来引用它们,从而实现了异常处理时的多态性。

User:

  • 如果 catch 中的参数不是引用,而是一个对象呢?情况将会是如何的?

ChatGPT:

  • 如果在 catch 块中的参数不是引用,而是一个对象,那么将会发生对象的拷贝。这意味着异常对象会被复制一份,而不是通过引用来访问原始的异常对象。
    具体来说,当抛出异常并且在调用栈中找到匹配的 catch 块时,如果异常的类型与 catch 块中指定的对象类型相匹配,那么会发生对象的拷贝,即异常对象的副本会被创建并传递给 catch 块。
    这种情况下,与使用引用相比,会产生额外的开销,因为需要进行对象的拷贝操作。通常情况下,建议在异常处理中使用引用而不是对象,以避免不必要的对象拷贝。

User:

  • 如果是这样,那么整个过程将会产生两次复制构造,一次是 throw,一次是 catch,对吗?

ChatGPT:

  • 是的,你说得对。如果在 catch 块中的参数是一个对象而不是引用,那么整个过程将会产生两次复制构造。
    因此,使用对象作为 catch 块的参数会导致两次复制构造,增加了额外的开销。相比之下,使用引用作为 catch 块的参数可以避免这种额外的开销,因为引用不会引发对象的复制,而是直接引用原始的异常对象。

可以看到 GPT 的回答很好,以下是我写得一个实例,来解释以上的所有信息:

#include <iostream>
#include <string>

int a = 0;

class Base {
    int data;

  public:
    Base(int d = 0) : data(d) {}
    virtual ~Base() { std::cout << "Base destructor called.\n" << std::endl; }
    virtual void display() const {
        std::cout << "Base base value:" << data << "\n" << std::endl;
    }
    int value() const { return data; }
    // 复制构造
    Base(const Base &other) : data(other.data) {
        a++;
        std::cout << "Base copy constructor called.\n" << std::endl;
    }
};

class Derived : public Base {
    char data;

  public:
    Derived(char c = 'a') : Base(), data(c) {}
    virtual ~Derived() {
        std::cout << "Derived destructor called.\n" << std::endl;
    }
    virtual void display() const override {
        std::cout << "Derived base value:" << Base::value() << std::endl;
        std::cout << "Derived derived value:" << data << "\n" << std::endl;
    }
    // 复制构造
    Derived(const Derived &other) : Base(other), data(other.data) {
        a++;
        std::cout << "Derived copy constructor called.\n" << std::endl;
    }
};

void throwException(bool useDerived) {
    if (useDerived) {
        Derived d('b');
        throw d;
    } else {
        Base b(100);
        throw b;
    }
}

int main() {
    try {
        // 使用基类的引用捕获异常

        // 调用一次基类复制构造+1
        // 调用一次派生类复制构造+1

        // 析构抛出对象
        try {
            std::cout << "---------------开始构造--------------" << std::endl;
            throwException(true);
        } catch (Base &b) {
            std::cout << "\na: " << a << std::endl;
            b.display(); // 多态调用
            // 生命周期结束,析构指向的对象
            std::cout << "---------------开始析构--------------" << std::endl;
        }

        // 调用一次基类复制构造+1
        // 调用一次派生类复制构造+1
        // 析构抛出对象

        // 由于catch()参数是对象
        // 继续调用基类复制构造+1

        try {
            std::cout << "---------------开始构造--------------" << std::endl;
            throwException(true);

        } catch (Base b) {
            std::cout << "\na: " << a << std::endl;
            b.display(); // 非多态调用,发生切片现象
            // 生命周期结束,析构临时对象,以及 b
            std::cout << "---------------开始析构--------------" << std::endl;
        }

        // 调用一次基类复制构造+1
        // 析构抛出对象
        try {
            std::cout << "---------------开始构造--------------" << std::endl;
            throwException(false);
        } catch (Base &b) {
            std::cout << "\na: " << a << std::endl;
            b.display();
            // 生命周期结束,析构指向的对象
            std::cout << "---------------开始析构--------------" << std::endl;
        }

        // 调用基类复制构造+1
        // 析构抛出对象
        // 由于 catch()参数是对象
        // 调用复制构造+1
        try {
            std::cout << "---------------开始构造--------------" << std::endl;
            throwException(false);
        } catch (Base b) {
            std::cout << "\na: " << a << std::endl;
            b.display();
            // 生命周期结束,析构临时复制对象,以及 b
            std::cout << "---------------开始析构--------------" << std::endl;
        }

    } catch (const std::exception &e) {
        std::cerr << "\nCaught exception: " << e.what() << std::endl;
    }

    return 0;
}


运行结果:

./main                      
---------------开始构造--------------
Base copy constructor called.

Derived copy constructor called.

Derived destructor called.

Base destructor called.


a: 2
Derived base value:0
Derived derived value:b

---------------开始析构--------------
Derived destructor called.

Base destructor called.

---------------开始构造--------------
Base copy constructor called.

Derived copy constructor called.

Derived destructor called.

Base destructor called.

Base copy constructor called.


a: 5
Base base value:0

---------------开始析构--------------
Base destructor called.

Derived destructor called.

Base destructor called.

---------------开始构造--------------
Base copy constructor called.

Base destructor called.


a: 6
Base base value:100

---------------开始析构--------------
Base destructor called.

---------------开始构造--------------
Base copy constructor called.

Base destructor called.

Base copy constructor called.


a: 8
Base base value:100

---------------开始析构--------------
Base destructor called.

Base destructor called.

可以看到catch()的参数尽管可以被设为引用,但是其实仍然要发生异常对象的复制(用复制构造函数)。如果没有被设置为引用,那么在最后,不仅要析构异常对象副本,还要析构异常对象本身。

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

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

相关文章

基于STM32的智能垃圾分类识别系统设计(论文)_kaic

摘 要 智能垃圾分类技术逐渐受到了政府的重视和支持&#xff0c;越来越多的城市开始推行垃圾分类政策。因此设计一款能够对垃圾进行识别并分类的控制系统具有一定的现实意义。本设计采用STM32单片机作为整个系统的控制核心&#xff0c;利用K210开发板作为图像识别控制系统&…

在Postgres中,如何有效地管理大型数据库的大小和增长

文章目录 一、定期清理和维护1. VACUUM和ANALYZE2. 删除旧数据和归档 二、分区表三、压缩数据四、配置优化1. 调整维护工作负载2. 监控和日志 五、使用外部存储和扩展1. 外部表和FDW2. 扩展和插件 六、定期备份和恢复测试结论 管理大型数据库的大小和增长是数据库管理员&#x…

如何看懂电路图,理解电流回路

任何电器都需要电源来供电。电源有正极(+)和负极(-),为了向负载提供电力,电流必须从正极流出,通过负载后再回到负极。这构成了一个供电电流回路,负载得到电力供应后才能开始工作。如果其中的某个环节断开,就无法形成供电电流回路,负载将得不到供电,也无法正常工作。 在一…

Typescript 总结3——类

一、是什么 类&#xff08;Class&#xff09;是面向对象程序设计&#xff08;OOP&#xff0c;Object-Oriented Programming&#xff09;实现信息封装的基础 类是一种用户定义的引用数据类型&#xff0c;也称类类型 传统的面向对象语言基本都是基于类的&#xff0c;JavaScript …

机器学习与深度学习 --李宏毅(笔记与个人理解)Day 20

Day 20 RNN 2 实际使用和其他应用 在实际的学习&#xff08;training&#xff09;过程中是如何工作的&#xff1f; step 1 Loss step 2 training Graindent Descent 反向传播的进阶版 – BPTT CLIpping 设置阈值~ 笑死昨天刚看完关伟说的有这玩意的就不是好东西 Why&#xff1…

window轻松使用k8s

Docker Desktop安装篇 1、win安装 1、下载安装包 https://www.docker.com/products/docker-desktop/ 官网下载安装包 2、配置win支持虚拟化 不勾选Hyper-V&#xff0c;它和Windows Subsystem for Linux (WSL) 是两套功能&#xff0c;这里不选他 3、安装WSL配置window支持lin…

2024电容笔专业对比评测:西圣、倍思、绿联哪款平替电容笔更好用?

在当今学习和工作环境中&#xff0c;iPad作为一种多功能的学习和生产力工具&#xff0c;受到越来越多人的青睐与需求。然而&#xff0c;要充分发挥iPad的功能&#xff0c;一个优质的电容笔是必不可少的配件之一。电容笔不仅可以帮助用户进行手写笔记、绘画创作&#xff0c;还能…

包装类的认识

前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&#x1…

48.基于SpringBoot + Vue实现的前后端分离-雪具销售系统(项目 + 论文PPT)

项目介绍 本站是一个B/S模式系统&#xff0c;采用SpringBoot Vue框架&#xff0c;MYSQL数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SpringBoot Vue技术的雪具销售系统设计与实现管理工作系统…

探索分布式系统监控zabbix-------------监控Windows

扩展windows 10 server2012 server2016 server2019 监控 一、在虚拟机中安装zabbix的客户端 下载网站 Download and install Zabbix 安装系统一直托不进虚拟机中&#xff1b;因为没安装Tools组件 点击虚拟机&#xff0c;选择安装VMware Tools 查看主机名 二、在web页…

ArcGIS在洪水灾害普查、风险评估及淹没制图中的技术应用

2020年国务院办公厅印发《关于开展第一次全国自然灾害综合风险普查的通知》&#xff0c;定于2020年至2022年开展第一次全国自然灾害综合风险普查。水旱灾害风险普查是全国自然灾害综合风险普查的重要组成部分。其中&#xff0c;我国有超过 60%的国土面积、90%以上的人口均受到不…

FFmpeg: 自实现ijkplayer播放器--01项目简介

文章目录 项目介绍流程图播放器实现过程界面展示项目代码 项目介绍 此项目基于FFmeg中 ffplay.c进行二次开发&#xff0c;实现基本的功能&#xff0c;开发软件为Qt 项目优势&#xff1a; 参考ijkplayer播放器&#xff0c;实现UI界面和播放器核心进行解耦&#xff0c;容易添加…

SpringBoot3 函数式web 小记

说明&#xff1a;函数式web是spring5.2之后的一个新特性&#xff0c;Spring Boot 3 进一步优化了这一模型&#xff0c;为开发现代 Web 应用提供了更加灵活、简洁的方法&#xff1b; 函数式web的四大核心对象 - RouterFunction&#xff1a;定义路由信息 - RequestPredicates&am…

15_SpringBoot

文章目录 SpringBoot创建SpringBoot应用官网IDEApom.xml文件启动类 整合SpringMVC整合配置类静态资源处理FilterTomcat其他配置 整合MyBatis约定大于配置的原理配置文件中的值的获取yml形式的配置文件约定大于配置的说明注解配置文件配置项 SpringBoot SpringBoot简化Spring阶…

强化网络安全防线,您的等级保护措施到位了吗?

在这个信息化飞速发展的时代&#xff0c;网络安全已经成为我们每个人都需要关注的问题。无论是企业还是个人&#xff0c;我们的工作和生活都越来越依赖于网络。确保网络环境的安全&#xff0c;防止信息泄露和网络攻击&#xff0c;已经成为了一项至关重要的任务。等级保护制度作…

现货白银的止损:原始止损和移动止损

止损是我们做现货白银必备的工具&#xff0c;它的主要功能是控制投资者的亏损&#xff0c;进而控制我们在交易中的风险。而现货白银的止损主要有两种&#xff0c;一个是原始止损&#xff0c;另外一个是移动止损。 原始止损是我们现货白银止损的基本方法。原始止损的意思就是初次…

Git回滚版本并push到远端master

1、查看日志 git log 2、还原最近的版本 () --git reset --hard commit-id 如&#xff1a;git reset --hard d84da14bf2743683eca7a015f56114faaa344f42 3、覆盖分支版本 git push -f origin dev 回滚本地master完成后&#xff0c;将回滚后的代码push到远端master&#xf…

C++ | Leetcode C++题解之第25题K个一组翻转链表

题目&#xff1a; 题解&#xff1a; class Solution { public:// 翻转一个子链表&#xff0c;并且返回新的头与尾pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {ListNode* prev tail->next;ListNode* p head;while (prev ! tail) {ListN…

C++练级之路——类和对象(中二)

1、运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也是具有其返回值类型&#xff0c;函数名字以及参数列表&#xff0c;其返回值类型和参数列表与普通的函数类似。 函数名字为&#xff1a;关键字operator后面接需…

华为ensp中静态路由和默认路由的原理及配置

作者主页&#xff1a;点击&#xff01; ENSP专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年4月17日17点37分 默认路由 [Router] ip route-static <目的网络> <目的网络掩码> <下一跳地址>默认路由的作用是将无法匹配路由表中其他路由表项的…
最新文章