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

news2025/4/25 22:07:51

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.coloradmin.cn/o/1588028.html

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

相关文章

【ROS2笔记一】ROS2的基本组件

1.ROS2的基本组件 与ROS1类似的&#xff0c;ROS2也具有node&#xff0c;topic&#xff0c;service&#xff0c;action之类的组件&#xff0c;并且也具有rqt等工具。 可以像使用ROS1的命令行的方式&#xff08;参这里【ROS学习笔记7】ROS中的常用命令行&#xff09;&#xff0…

Python机器学习学习线路

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

小程序 SSL证书的重要性与选择

随着移动互联网的迅猛发展&#xff0c;微信小程序已成为众多企业和开发者连接用户的重要平台。然而&#xff0c;随之而来的是对数据安全和隐私保护的严峻挑战。在这一背景下&#xff0c;小程序SSL证书的作用变得尤为重要&#xff0c;它为小程序提供了一个安全的通信管道&#x…

神策PRO PCIe 5.0 SSD发布,光威白菜级定价,打破海外品牌价格壁垒

记得去年DDR5刚刚流行的时候&#xff0c;光威就推出了不少平民价位的DDR5内存产品&#xff0c;加速了DDR5内存的普及&#xff0c;最近光威再接再厉&#xff0c;又拿出了神策PRO PCIe 5.0 SSD&#xff0c;这一举措不仅展现了企业的创新实力&#xff0c;看来是又要来普及PCIe 5.0…

蓝桥杯 — — RSA解密

RSA解密 友情链接&#xff1a;RSA解密 题目&#xff1a; 思路&#xff1a; 对于这道题目&#xff0c;给出了三个已知量n d C&#xff0c;要我们进行解密&#xff0c;对于解密的公式 X C e m o d n X C^e \mod n XCemodn来讲&#xff0c;我们有唯一的参数e是未知的&#xf…

标定系列——Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译(二十)

Ubuntu18.04下opencv-4.5.3与opencv_contrib-4.5.3源码编译 说明下载安装步骤1.更新2.安装必要的依赖包3.下载源码包并解压4.终端运行如下命令5.添加配置路径6.验证安装是否成功 说明 Ubuntu18.04下对opencv-4.5.3与opencv_contrib-4.5.3源码编译 下载 CSDN下载 安装步骤 …

Qt创建基于应用程序的插件

应用程序插件 什么是插件插件的好处插件的种类应用程序插件创建应用程序的插件步骤:创建测试插件的应用程序步骤:应用程序插件示例开发环境创建示例生成插件运行结果总结什么是插件 插件是一种用于应用程序功能扩展和增强,且按照特定规范编写的应用程序接口的程序。 插件的…

【应急响应事件】记一次矿机木马事件

事情起因&#xff0c;是因为实验室有一台服务器的占用率从开机启动就是100%&#xff0c;很怀疑就是中了某种矿机木马&#xff0c;拿去挖矿了&#xff0c;然后经过师兄的不懈努力&#xff0c;终于找到了木马文件&#xff0c;给他命名为virus_sample 然后我就拿着样本去逆了 木马…

Centos7在线安装mysql5.7

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 安装Mysql yum源1、卸载旧环境2、下载mysql yum源3、上传到自己服务器1&#xff09;、上传源2&#xff09;、安装yum源3&#xff09;、查看yum源是否安装成功 安装M…

【Java核心技术】第3章 Java的基本程序设计结构

1 数据类型 Java一共有8种数据类型&#xff1a; 4种整型 类型存储需求int4字节short2字节long8字节byte1字节 2种浮点型 类型存储需求float4字节double8字节 1种字符型 1种布尔型 2 变量声明 2.1 局部类型推断 如果可以从变量的初始值推断变量类型&#xff0c;只需要使用…

Linux网络基础2(下)

传输层 再谈端口号端口号的划分netstatpidof UDP协议 UDP的特点UDP缓冲区UDP使用注意事项UDP报头的理解基于UDP的应用层协议 TCP协议 4位首部长度16位窗口大小确认应答机制32位序号和32位确认序号6个标记位超时重传机制连接管理机制流量控制快重传机制再谈序号延迟应答面相字节…

Day16_学点儿JavaEE_实践_基于IDEA2023的简易JavaWeb项目、Tomcat输出乱码解决

0 JavaWeb项目目录 └──JavaWeb├──resources│ └──db.properties├──src│ └──com.sdust.web│ ├──servlet│ │ └──StudentServlet│ ├──pojo│ │ └──Student│ └──util│ └──JDBCUtil├──web│ ├──st…

电商技术揭秘十三:云计算在电商中的应用场景

相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xff1a;电商平台…

百科引流攻略|小马识途分享百科营销的五个技巧

纵观整个互联网领域&#xff0c;国内几大巨头百度、抖音、腾讯都布局了自身的百科平台&#xff0c;百科营销也始终作为网络营销一个重要分支而存在。很多人都知道百科营销是品牌背书的一把王牌&#xff0c;但很少有人提及百科营销的引流作用。 有人可能会说&#xff0c;百科词条…

[数据结构]—二叉树基本概念

1.树概念及结构 1.树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff…

老房改造系列--如何用一套流程接入所有业务线

ToB业务没有太多高并发的挑战&#xff0c;但同一套流程往往可能需要承载各种差异化的复杂业务需求&#xff0c;所以如何让系统具备良好的扩展性成为ToB业务系统最大的挑战。本文将详细讲述如何用一套流程接入所有业务线&#xff1f; 老系统改造不是一蹴而就的&#xff0c;从20…

【自用笔记】【大数据】

1 mapreduce &#xff08;1&#xff09;Map任务的数量&#xff1a;由输入数据的大小决定的&#xff0c;如文件数量和大小、HDFS块大小以及FileInputFormat的设置等。每个MapSlot可以运行一个Map任务 &#xff08;2&#xff09;Reduce任务的数量&#xff08;分区数&#xff09;&…

想走?可以!先买票--迭代器模式

1.1 乘车买票&#xff0c;不管你是谁&#xff01; 售票员检查谁没有买票&#xff0c;把车厢里的人都遍历一遍。 1.2 迭代器模式 迭代器模式&#xff08;Iterator&#xff09;&#xff0c;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露该对象的内部表示…

[CSS]布局

盒子就是把网站分割成一小块一小块的吧&#xff0c;然后方便移动或者管理 背景 属性名描述background-color设置元素的背景颜色。background-image设置元素的背景图片。背景图片与背景颜色同时设置时&#xff0c;则图片覆盖颜色。写法如下&#xff1a;background-image: url(&…

【洛谷 P4017】最大食物链计数 题解(深度优先搜索+动态规划+邻接表+记忆化搜索+剪枝)

最大食物链计数 题目背景 你知道食物链吗&#xff1f;Delia 生物考试的时候&#xff0c;数食物链条数的题目全都错了&#xff0c;因为她总是重复数了几条或漏掉了几条。于是她来就来求助你&#xff0c;然而你也不会啊&#xff01;写一个程序来帮帮她吧。 题目描述 给你一个…