【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的概念,还是比较重要的,我会在下一节重点讲解。希望大家可以对异常能有个很好的理解。

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

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

相关文章

使用Java+Maven+TestNG进行自动化测试

写作背景&#xff1a;有点Java基础的功能测试人员&#xff08;点点点工程师&#xff09;&#xff0c;所在项目有"去QE"的趋势&#xff0c;所以自己要多点亮其他技能&#xff0c;让路子走宽点。 简单说一下去QE&#xff1a;项目测试不再有专职的测试工程师来做&#x…

计算机网络——40各个层次的安全性

各个层次的安全性 安全电子邮件 Alice需要发送机密的报文m给Bob Alice 产生随机的对称秘钥&#xff0c; K s K_s Ks​使用 K s K_s Ks​对报文进行加密&#xff08;为了效率&#xff09;对 K s K_s Ks​使用Bob的公钥进行加密发送 K s ( m ) K_s(m) Ks​(m)和 K B ( K S ) K…

小程序/app/H5多端圈子社区论坛系统交友/社交/陌生人社交即时聊天私域话题社区论坛 行业圈子小程序 微信社区小程序圈子论坛社区小程序

项目介绍 这是一个社区论坛类小程序项目源码&#xff0c;可以实现用户发送自定义图文内容&#xff0c;点赞&#xff0c;评论&#xff0c;回复&#xff0c;记录评论过的帖子&#xff0c;记录发表过的帖子&#xff0c;左滑删除&#xff0c;在线实时接收消息&#xff0c;离线接收…

MySQL高级篇(索引概述、优缺点、结构 B+Tree)

目录 1、索引概述 2、索引优缺点 3、索引的结构 1、索引概述 介绍&#xff1a;索引&#xff08;index&#xff09;是帮助MySQL 高效获取数据 的 数据结构&#xff08;有序&#xff09;。在数据之外&#xff0c;数据库系统还维护着满足特定查找算法的数据结构&#xff0c;这些数…

分布式系统:缓存与数据库一致性问题

前言 缓存设计是应用系统设计中重要的一环&#xff0c;是通过空间换取时间的一种策略&#xff0c;达到高性能访问数据的目的&#xff1b;但是缓存的数据并不是时刻存在内存中&#xff0c;当数据发生变化时&#xff0c;如何与数据库中的数据保持一致&#xff0c;以满足业务系统…

java实现TCP交互

服务器端 import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.PriorityQueue; import java.util.Scanner;public class TCP_Serv…

(文章复现)考虑网络动态重构的分布式电源选址定容优化方法

参考文献&#xff1a; [1]朱俊澎,顾伟,张韩旦,等.考虑网络动态重构的分布式电源选址定容优化方法[J].电力系统自动化,2018,42(05):111-119. 1.摘要 以投资周期经济收益最高为目标&#xff0c;基于二阶锥规划提出了一种考虑网络动态重构的分布式电源选址定容优化方法。首先&am…

OpenStack (T)部署trove

环境&#xff1a;Openstack&#xff08;T&#xff09; CentOS Linux release 7.9.2009 (Core) 正文&#xff1a; 1.控制节点安装trove软件包 # yum install openstack-trove-guestagent openstack-trove python-troveclient openstack-trove-ui –y2.创建数据库&#xff0c…

动态代理 --java学习笔记

什么是动态代理&#xff1f; 当一个类的很多方法都存在重复冗杂的部分&#xff0c;就可以使用代理来处理那些重复部分的任务&#xff0c;到了各自的实现部分再丢回给原方法处理&#xff0c;同时也可以提高方法的扩展性&#xff0c;而动态则是指在运行时动态地创建代理对象&…

【高德地图笔试题汇总】2024-04-11-高德地图春招笔试题-三语言题解(CPP/Python/Java)

&#x1f36d; 大家好这里是KK爱Coding &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新高德地图近期的春秋招笔试题汇总&#xff5e; &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&…

【感谢】心怀感恩,共赴知识之旅——致每一位陪伴我突破百万总访问量的您

小伙伴朋友们&#xff1a; 此刻&#xff0c;我怀着无比激动与深深感激的心情&#xff0c;写下这篇特别的博文。今天&#xff0c;我的CSDN总访问量成功突破了百万大关&#xff0c;这不仅是一个数字的跨越&#xff0c;更是你们对我的支持、信任与鼓励的有力见证。在此&#xff0…

前端Vue自定义勾选协议组件的开发与应用

摘要&#xff1a; 随着前端技术的不断发展&#xff0c;用户体验成为了软件开发中的关键要素。在登录、注册等场景中&#xff0c;勾选协议是常见的需求。本文旨在介绍一款基于 Vue.js 的自定义勾选协议组件的开发与应用&#xff0c;该组件适用于多种场景&#xff0c;并且具备良…

图形学基础:二维三维刚体的移动、缩放和旋转矩阵

一、二维 1.1 缩放矩阵 x&#xff0c;y分别表示在x轴&#xff0c;y轴缩放的倍数 示例&#xff1a; 点(2,1)在x&#xff0c;y轴上分别缩放x倍&#xff0c;y倍 1.2 平移矩阵 x&#xff0c;y分表表示在x轴&#xff0c;y轴上移动的距离 示例&#xff1a;点&#xff08;2,1&#xf…

C语言——指针的高级引用

目录 1.概述 2.虚拟内存空间 2.1存储期限 2.2栈区管理 2.3堆区域的使用 3.动态内存分配和释放&#xff08;重点&#xff09; 3.1通用指针类型void 3.2内存分配malloc函数 3.2.1 malloc函数&#xff08;memory allocation&#xff09;&#xff08;注意len*size&#xff…

工智能图像降噪软件 ON1 NoNoise AI 2024 for Mac激活版

ON1 NoNoise AI 2024 for Mac是一款专为Mac用户设计的先进人工智能图像降噪软件。其核心功能在于能够利用机器学习技术&#xff0c;快速并智能地消除图像中的噪点&#xff0c;无论是亮度噪点还是颜色噪点&#xff0c;都能得到显著的改善。 软件下载&#xff1a;ON1 NoNoise AI …

【高项】信息化发展

目录 1.1 信息与信息化 1.1.1 信息 1.信息的定义 2.信息的特征与质量 1.1.2 信息系统 1.信息系统及其特性 2.信息系统生命周期 1.1.3 信息化 1.信息化内涵 2.信息化体系&#xff08;口诀&#xff1a;上应下技左人右规&#xff0c;中资网&#xff09; 1.2 现代化基础…

Vue笔记 2

数据代理 数据代理&#xff1a;通过一个对象代理对另一个对象中属性的操作&#xff08;读/写&#xff09; let obj{x:100} let obj2{y:200} Object.defineProperty(obj2,x,{get(){return obj.x},set(value){obj.x value} })Vue中的数据代理 Vue中的数据代理&#xff1a; 通…

【go从入门到精通】作用域,包详解

作者简介&#xff1a; 高科&#xff0c;先后在 IBM PlatformComputing从事网格计算&#xff0c;淘米网&#xff0c;网易从事游戏服务器开发&#xff0c;拥有丰富的C&#xff0c;go等语言开发经验&#xff0c;mysql&#xff0c;mongo&#xff0c;redis等数据库&#xff0c;设计模…

u盘为什么一插上电脑就蓝屏,u盘一插电脑就蓝屏

u盘之前还好好的,可以传输文件,使用正常,但是最近使用时却出现问题了。只要将u盘一插入电脑,电脑就显示蓝屏。u盘为什么一插上电脑就蓝屏呢?一般,导致的原因有以下几种。一,主板的SATA或IDE控制器驱动损坏或安装不当;二,电脑系统分区存在磁盘或文件故障错误;三,电脑中…

【力扣】125.验证回文串

刷题&#xff0c;过了真的好有成就感&#xff01;&#xff01;&#xff01; 题解&#xff1a; 根据题目要求&#xff0c;我们需要处理一下几个问题&#xff1a; 将大写字母转变成小写对原来的字符串进行处理&#xff0c;只要字母和数字考虑只有一个和字符串为空的情况 1、将…