【C++进阶】C++异常详解

C++异常

  • 一,传统处理错误方式
  • 二,C++处理的方式
  • 三,异常的概念
  • 四,异常的使用
    • 4.1 异常和捕获的匹配原则
    • 4.2 函数调用链中异常栈展开匹配原则
    • 4.3 异常的重新抛出(异常安全问题)
    • 4.4 RAII思想在异常中的作用
  • 五,C++标准库的异常体系
  • 六,自定义异常体系
  • 七,异常规范
  • 八,总结

一,传统处理错误方式

在讲处理错误之前,我们先来看一个例子:
当我们在做除法运算时,如果被除数为0,那么就会报错,所以我们加上assert断言,如果time为0则程序退出。

double Division(int len, int time)
{assert(time != 0);return len / time;
}int main(){Division(1,0);return 0;
}

在C语言中,我们会加上assert断言,如果time为0则程序退出。
或者返回错误码,也就是函数运行完后查看返回码,但是需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中表示错误的。

这些处理错误的方式是比较暴力的,因为会直接终止掉程序,如果一个程序部署在服务器上,一旦出现错误就退出的话,那么这个错误造成的损失是非常的大。

所以C++引入了新的处理错误的方式,让程序可以不用再退出。

二,C++处理的方式

C++的解决办法是捕获异常。这里如果time如果为0的话,就抛出了一个字符串。在下面的main函数中捕获抛出的字符串。如果time为0时,main函数就会执行到catch里的语句。这样就不会出现程序直接异常退出了。

double Division(int len, int time)
{if (time == 0){throw "除0错误";}else{return (double)len / (double)time;}
}int main() {try {Division(1, 0);}catch (const char* s) {cout << s << endl;}return 0;
}

三,异常的概念

异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者去处理这个错误

1. 这里的有个新的关键字throw,意思是抛出错误。抛出的错误会被catch捕获。
2. catch是在想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常,可以有多个catch进行捕获。

try
{// 保护的标识代码
}catch( ExceptionName e1 )
{// catch 块
}catch( ExceptionName e2 )
{// catch 块
}catch( ExceptionName eN )
{// catch 块
}

3. try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块

四,异常的使用

4.1 异常和捕获的匹配原则

捕获异常不是随便捕获的,有下面几个原则:
1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码

double Division(int len, int time)
{if (time == 0){string s("除0错误");//这里抛出一个对象throw s;//}else{return (double)len / (double)time;}
}int main() {try {Division(1, 0);}catch (const char* s) {cout << s << endl;}catch (const string& s) {cout << s << endl;}return 0;
}

以上面代码为例,当出现异常后,这里的异常会激活第二个catch,而不是第一个,因为抛出的是一个string对象,第一个catch捕获的是字符串,和抛出的对象类型不匹配。

2. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回

还是以上面的代码为例,

double Division(int len, int time)
{if (time == 0){string s("除0错误");//这里抛出一个对象throw s;//}else{return (double)len / (double)time;}
}

就是说抛出的string这里其实抛出的是s的临时拷贝(移动拷贝),因为s出了这个作用域会销毁

3. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个

double Division(int len, int time)
{if (time == 0){throw "除0错误";}else{return (double)len / (double)time;}
}void func() {try {int len, time;cin >> len >> time;Division(len, time);}catch (const char* s) {cout << s << endl;}cout << "aaaaaaaaaaaaaa" << endl;
}int main() {try {func();}catch (const char* s) {cout << s << endl;}return 0;
}

这里分别在main函数中和func中捕获了异常,会执行哪里的catch的语句呢?
如果执行的是main中的,那么输出字符串后程序就会正常退出。
如果执行的是func中的,那么就会向下再执行那句打印"aaaaaaaaaa…"

我们来看一下结果:

在这里插入图片描述
这里也就验证了当有多个try catch时会匹配离异常近的。

4. catch(…)可以捕获任意类型的异常,问题是不知道异常错误是什么

int main() {try {func();//f1();}catch (const char* s) {cout << s << endl;}catch (const string &s) {cout << s << endl;}catch (...) {//cout << "位未知异常" << endl;}return 0;
}

如果捕获到未知异常 ,也就说明程序走到这里时有人没按规定抛异常。因为在一个项目组里都会统一规定如何抛异常,如果没有按照规定抛,则就会被最后的catch捕获。


4.2 函数调用链中异常栈展开匹配原则

函数在使用的时候会建立栈帧,所以调用函数就会产生调用链,如果出现了异常,那么这个调用链就会发生跳转
没有捕获异常时的调用栈:
在这里插入图片描述
有捕获异常的调用栈:
找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行
在这里插入图片描述

4.3 异常的重新抛出(异常安全问题)

异常的重新抛出就是在捕获到异常后,再将这个异常抛出去。有可能单个的catch不能完全处理一个异常,在进行一些校正处理以后,希望再交给更外层的调用链函数来处理,catch则可以通过重新抛出将异常传递给更上层的函数进行处理

看下面的例子:
在Func函数捕获异常之前,new了一个array数组。当main函数捕获到异常后,执行语句就会跳转到catch的地方,执行完后会退出程序。

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}
void Func()
{int* array = new int[10];int len, time;cin >> len >> time;cout << Division(len, time) << endl;cout << "delete []" << array << endl;delete[] array;}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}return 0;
}

没有异常时的执行:
在这里插入图片描述
捕获到异常后的执行结果:
在这里插入图片描述

这里就出现了一个比较坑的问题就是出现了内存泄漏,捕获到异常后,没有执行Func中的delete,没有将数组释放掉。

所以就需要将捕获到的异常重新抛出,交给外层去处理,这里捕获到后将数组释放掉

void Func()
{int* array = new int[10];try {int len, time;cin >> len >> time;cout << Division(len, time) << endl;}catch (...){cout << "delete []" << array << endl;delete[] array;throw;//捕到什么抛什么}// ...cout << "delete []" << array << endl;delete[] array;
}

这里在Func中捕获异常是捕获任意类型的异常,然后可以不到什么抛什么,直接交给外层去处理就行。

这里捕获到后,释放了new的空间,然后又在main函数中捕获到异常,处理完后正常退出。


但是又有一个更坑的问题,如果Func中的函数中new了好多个数组呢,难道要一个个去释放吗?
这显然是不合理的。
这就需要智能指针来解决了。


这里说一下还要注意的地方:

  1. 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化

  2. 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)

4.4 RAII思想在异常中的作用

RAII思想就是借助对象的生命周期来控制资源(解决异常安全问题)

接着上面的例子,借助智能指针来解决,这个智能指针我们在下一节进行讲解。
智能指针其实就是RAII思想的一种实现。这些我们统一在下一节讲解。

五,C++标准库的异常体系

C++ 提供了一系列标准的异常,定义在 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

在这里插入图片描述
在这里插入图片描述
感兴趣大家可以自己去学习了解一下:C++异常

六,自定义异常体系

但是因为一些原因,C++的异常体系设计的不是那么的好用,所以大多数都是我们自己去
用第三方的库或者自己实现一个异常体系。

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。

在这里插入图片描述

七,异常规范

这里再说一下程序中抛异常的规范。
因为在实践中,往往是一个项目组共同进行开发,大家都会去抛异常,这样就会导致物化八门,那么如何知道这个函数有没有抛出异常呢?

异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。

这里可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型
函数的后面接throw(),表示函数不抛异常


// 这里表示这个函数会抛出A/B/C/D中的某种类型的异常
void fun() throw(A,B,C,D);
// 这里表示这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这里表示这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw()

函数后面加上这些throw(类型),其实就相当于提醒作用,编译器并不会去强制的不捕获异常。不加也是可以的。


C++11中新增的noexcept,表示不会出现异常

void* operator delete (std::size_t size, void* ptr) noexcept;

但是和throw()不同的是,这里加上后如果出现异常,程序就不会捕获这里的异常。那么程序就会异常退出

八,总结

这里C++ 异常的讲解就到这里了,我们引出了智能指针的概念和RAII的概念,还是比较重要的,我会在下一节重点讲解。希望大家可以对异常能有个很好的理解。

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

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

相关文章

2024 Mathorcup高校数学建模挑战赛(B题)| 甲骨文识别 | 建模秘籍文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;以CNN卷积神经网络&#xff0c;计算机视觉等强大工具&#xff0c;构建了解决复杂问题的独特方案。深度学习, 混沌模型的妙用&#xff0c;为降低非法野生动物贸易提供新视角。通过综合分析&#xff0c;描…

IP广播对讲系统停车场解决方案

IP广播对讲系统停车场解决方案 一、需求分析 随着国民经济和社会的发展&#xff0c; 选择坐车出行的民众越来越多。在保护交通安全的同时&#xff0c;也给停车场服务部门提出了更高的要求。人们对停车场系统提出了更高的要求与挑战&#xff0c; 需要停车场系统提高工作效率与服…

01-Git 之快速入门操作本地仓库

https://learngitbranching.js.org/?localezh_CN在线练习git 1. Git 安装好Git以后, 先检查是否已经绑定了用户名和邮箱 git config --list1.1 为什么要使用版本控制&#xff1f; 从个人角度&#xff1a; 在做项目时&#xff0c;如果一点点去改代码会很乱&#xff0c;不利…

OpenCV4.9更多形态转换

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:OpenCV4.9处理平滑图像 下一篇:OpenCV4.9更多形态转换 基于这两者&#xff0c;我们可以对图像进行更复杂的转换。在这里&#xff0c;我们简要讨论 OpenCV 提供的 5 个操作&#xff1a; …

FreeBuf 全球网络安全产业投融资观察(3月)

综述 据不完全统计&#xff0c;2024年3月&#xff0c;全球网络安全市场共发生投融资事件53起&#xff0c;其中国内4起&#xff0c;国外49起。 3月全球络安全产业投融资统计表&#xff08;数据来源&#xff1a;航行资本、36氪&#xff09; 整体而言&#xff0c;国内4起投融资事…

AI写作软件哪个好?这4款好评如潮

在信息时代&#xff0c;AI技术的发展的日新月异&#xff0c;AI写作软件也因此诞生。特别是人们对于高效、便捷的写作工具需求日益增长&#xff0c;AI写作软件作为一种新兴的工具&#xff0c;在帮助人们提升写作效率、拓展创作思路方面发挥着越来越重要的作用。这些AI写作软件为…

C语言 函数——代码风格

目录 基本的代码规范 程序版式 对齐&#xff08;Alignment&#xff09;与缩进&#xff08;indent&#xff09; 变量的对齐规则 空行——分隔程序段落的作用 代码行内的空格——增强单行清晰度 代码行 长行拆分 标识符命名规则 标识符命名的共性规则 windows应用程序…

PostgreSQL入门到实战-第十八弹

PostgreSQL入门到实战 PostgreSQL中表连接操作(二)官网地址PostgreSQL概述PostgreSQL中表别名命令理论PostgreSQL中表别名命令实战更新计划 PostgreSQL中表连接操作(二) 了解PostgreSQL表别名及其实际应用程序。 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容…

19c数据库/dev/shm/过小导致pga内存不够

pga_aggregate_limit已经设置了120G&#xff0c;alert还是报内存不够 查询select * from v$pgastat&#xff0c;发现MGA占了80G内存 查看/dev/shm: 发现设置了7G&#xff0c;操作系统是512G&#xff0c;正常情况下/dev/shm应该是操作系统的一半&#xff0c;修改为250G后数据库…

微信小程序 django+nodejs电影院票务售票选座系统324kd

小程序Android端运行软件 微信开发者工具/hbuiderx uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 前端&#xff1a;HTML5,CSS3 VUE 后端&#xff1a;java(springbootssm)/python(flaskdja…

vue3:菜单、标签页和面包屑联动效果

文章目录 1.整体思路2.实现过程 概要 提示&#xff1a;这里可以添加技术概要 例如&#xff1a; openAI 的 GPT 大模型的发展历程。 1.整体思路 在之前做的后台项目中&#xff0c;菜单、标签页和面包屑之间的联动&#xff0c;自己都是通过在路由前置守卫中&#xff0c;定义b…

微服务面试题二

1.什么是雪崩 微服务之间相互调用&#xff0c;因为调用链中的一个服务故障&#xff0c;引起整个链路都无法访问的情况。 如何解决雪崩&#xff1f; 超时处理&#xff1a;请求超时就返回错误信息&#xff0c;不会无休止等待仓壁模式&#xff1a;限定每个业务能使用的线程数&a…

WPS的JS宏如何批量实现文字的超链接

表格中需要对文字进行超链接&#xff0c;每个链接指引到不同的地址。例如&#xff1a; 实现如下表格中&#xff0c;文件名称超级链接到对应的文件路径上&#xff0c;点击对应的文件名称&#xff0c;即可打开对应的文件。 序号文件名称文件路径1变更申请与处理表.xls文档\系统…

第十三届蓝桥杯省赛大学B组编程题(c++)

D.刷题统计 二分(AC): 注意:二分时右边界 right 的确定 #include<iostream> using namespace std; long long a,b,n; bool check(long long x){long long tx/7;x%7;long long temp0;if(x<5) tempx*a;else temp5*a(x-5)*b;long long cntt*(5*a2*b)temp;return cnt&g…

MySOL之旅--------MySQL数据库基础( 2 )

本篇碎碎念:尽自己最大的努力,直到筋疲力尽为止,加油 今日份励志文案: 别人都在前进,我为什么要停下 目录 补上一条博客缺失的内容 常用数据类型 数值类型&#xff1a; 字符串类型&#xff1a; 日期/时间类型&#xff1a; 二进制类型&#xff1a; 其他类型&#xff1a; …

抖音小店入驻有什么条件?资金少,没经验的普通人做得起吗?

大家好&#xff0c;我是电商花花。 在直播电商的推动下&#xff0c;抖音小店独特的电商模式下吸引着众多的商家&#xff0c;吸引着一波又一波的创业者入驻&#xff0c;想要在抖音小店上开垦出属于自己的电商净土。 想要入驻抖音小店还需要一些条件&#xff0c;然后才能入驻成…

Python机器学习学习线路

随着人工智能技术的飞速发展&#xff0c;机器学习已经成为计算机科学领域的热门话题。Python&#xff0c;作为一门功能强大且易于上手的编程语言&#xff0c;成为学习机器学习的理想选择。本文将为您介绍一条Python机器学习的学习线路&#xff0c;帮助您逐步掌握机器学习的基础…

ARM/X86+FPGA轨道交通/工程车辆行业的解决方案

深圳推出首条无人驾驶地铁—深圳地铁20号线&#xff0c;可以说是深圳地铁的一次开创性的突破。智能交通不断突破的背后&#xff0c;需要很严格的硬件软件等控制系 统&#xff1b;地铁无人驾驶意味着信号系统、通信系统、综合监控系统、站台屏蔽门工程等项目必须严格执行验收。…

ping命令返回无法访问目标主机和请求超时浅析

在日常经常用ping命令测试网络是否通信正常&#xff0c;使用ping命令时也经常会遇到这两种情况&#xff0c;那么表示网络出现了问题。 1、请求超时的原因 可以看到“请求超时”没有收到任何回复。要知道&#xff0c;IP数据报是有生存时间的&#xff0c;当其生存时间为零时就会…

goproxy一键安装脚本(稳定易用的proxy软件)

goproxy 官网 https://goproxy.cn/ go语言开发的简单易用高性能proxy 软件 #!/bin/bash # time: 2021-05-11 17:47:39 # by: Chen ##执行脚本需要传入网络设备名 ##例&#xff1a;sh goproxy-install.sh eth0# 0.安装必须要的依赖 yum install wget -y || apt install wget -y…