一文搞定C++异常机制(附代码+详细解析)

0

C++异常

  • 1.引文
    • C语言传统的处理错误的方式:
  • 2.C++异常概念
  • 3.异常的使用
    • 3.1 异常的抛出和捕获
    • 3.2 异常的重新抛出
      • 异常捕获中的内存泄漏问题
    • 3.3异常安全
    • 3.4异常规范
  • 4.异常优缺点
  • 5.总结:

1.引文

C语言传统的处理错误的方式:

  1. 终止程序,如assert,缺陷:用户难以接受。如发生内存错误,除0错误时就会终止程序。
  2. 返回错误码,缺陷:需要程序员自己去查找对应的错误。如系统的很多库的接口函数都是通过把错误码放到errno中,表示错误
  3. C 标准库中setjmp和longjmp组合。这个不是很常用,了解一下实际中C语言基本都是使用返回错误码的方式处理错误,部分情况下使用终止程序处理非常严重的错误。

2.C++异常概念

  • 异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。
  • throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
  • catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异常,可以有多个catch进行捕获。
  • try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。
  • 如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可能抛出异常的代码,try 块中的代码被称为保护代码。使用 try/catch 语句的语法如下所示:
try
{
// 保护的标识代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}

3.异常的使用

3.1 异常的抛出和捕获

  • 异常的抛出和匹配原则
  1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。

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

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

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

  5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,使用基类捕获,这个在实际中非常实用,我们后面会详细讲解这个。

  • 在函数调用链中异常栈展开匹配原则
  1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则调到catch的地方进行处理。
  2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
  3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的catch子句的
    过程称为栈展开。所以实际中我们最后都要加一个catch(…)捕获任意类型的异常,否则当有异常没捕获,程序就会直接终止。
  4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。
    pic 1
  • 代码测试
#define _CRT_SECURE_NO_WARNINGS
using namespace std;
#include<iostream>
double devision(int a,int b)
{
	if (b == 0)
		throw "Division by zero condition!";
	else
		return (double)a / (double)b;
}

void Func()
{
	int x, y;
	cin >> x >> y;
	cout << devision(x, y) << endl;
}
int main()
{
	try {
		Func();
	}
	//catch (const char* errmgs)
	//{
	//	cout << errmgs << endl;
	//}
	catch (...)
	{
		cout << "捕获未知异常!" << endl;
	}
	return 0;
}

上述代码中,我们定义:当被除数b为0时为异常事件,进行捕获,在main函数里进行调用,如果有匹配的catch 系统就会优先捕获,

//catch (const char* errmgs)
	//{
	//	cout << errmgs << endl;
	//}
  • 情况1 :这段代码未注释之前,当b的参数为0 是异常捕获如下:
    pic 2

当我们定义好匹配的catch时,系统会直接调用匹配选项捕获异常,从而跳过下面的异常处理。

  • 情况二:
    当没有匹配的catch时
    pic3
    catch(…)会捕获到任意类型的异常,当时不知道异常的定位及原因。

  • 情况三:
    把catch(…)放前面
    pic 4
    会报错 就近原则,先走catch(…)已经把所有异常捕获了就不会继续捕获。

  • 情况4

pic4
在前面try 当输入a=0时抛出1111;这里,当第一个异常没出现会接着往下运行,但是遵守就近原则,一出现就抛出!

3.2 异常的重新抛出

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

异常捕获中的内存泄漏问题

  • 先看这样一段代码:
    pic5
  • 在Func2中,我们申请了一个内存,但是在devision出现异常的时候我们直接就跳到了对于的catch,而没有对新开的这个空间进行释放,就会造成资源的泄漏。
  • 我们把代码循环跑起来,发现系统的资源在不断的上升,这就是内存泄漏造成的。

pic 6
解决机制:

  • 重新抛出
    在Func2中做处理,用catch(…)捕获异常,当捕获到异常时,先释放array
    代码:
try
	{
		int len, time;
		//cin >> len >> time;
		len = rand();
		time = rand() % 5;
		cout << devision(len, time) << endl;
	}
	catch (...)
	{
		// 释放
		cout << __LINE__ << " delete []" << array << endl;
		delete[] array;
		throw; // 捕获什么抛什么
	}

	cout << __LINE__ << " delete []" << array << endl;
	delete[] array;
  • 智能指针
    后面会介绍。

3.3异常安全

  • 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,否则可能导致对象不完整或没有完全初始化
  • 析构函数主要完成资源的清理,最好不要在析构函数内抛出异常,否则可能导致资源泄漏(内存泄漏、句柄未关闭等)
  • C++中异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏,在lock和unlock之间抛出了异常导致死锁,C++经常使用RAII来解决以上问题,关于RAII我们智能指针这节进行讲解

3.4异常规范

  1. 异常规格说明的目的是为了让函数使用者知道该函数可能抛出的异常有哪些。 可以在函数的后面接throw(类型),列出这个函数可能抛掷的所有异常类型。
  2. 函数的后面接throw(),表示函数不抛异常。
  3. 若无异常接口声明,则此函数可以抛掷任何类型的异常

4.异常优缺点

C++异常的优点:

  1. 异常对象定义好了,相比错误码的方式可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序的bug。
  2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那么我们得层层返回错误,最外层才能拿到错误,具体看下面的详细解释。
// 1.下面这段伪代码我们可以看到ConnnectSql中出错了,先返回给ServerStart,ServerStart再
返回给main函数,main函数再针对问题处理具体的错误。
// 2.如果是异常体系,不管是ConnnectSql还是ServerStart及调用函数出错,都不用检查,因为抛
出的异常异常会直接跳到main函数中catch捕获的地方,main函数直接处理错误。
int ConnnectSql()
{
// 用户名密码错误
if (...)
return 1;
// 权限不足
if (...)
return 2;
}
int ServerStart() {
if (int ret = ConnnectSql() < 0)
return ret;
int fd = socket()
if(fd < 0return errno;
}
int main()
{
if(ServerStart()<0)
...
return 0;
}
  1. 很多的第三方库都包含异常,比如boost、gtest、gmock等等常用的库,那么我们使用它们也需要使用异常。
  2. 很多测试框架都使用异常,这样能更好的使用单元测试等进行白盒的测试。
  3. 部分函数使用异常更好处理,比如构造函数没有返回值,不方便使用错误码方式处理。比如T&operator这样的函数,如果pos越界了只能使用异常或者终止程序处理,没办法通过返回值表示错误。
  • C++异常的缺点:
  1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会导致我们跟踪调试时以及分析程序时,比较困难。
  2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
  3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。
  4. C++标准库的异常体系定义得不好,导致大家各自定义各自的异常体系,非常的混乱。
  5. 异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。所以异常规范有两点:一、抛出异常类型都继承自一个基类。二、函数是否抛异常、抛什么异常,都使用 func()throw();的方式规范化。

5.总结:

异常总体而言,利大于弊,所以工程中我们还是鼓励使用异常的。另外OO的语言基本都是用异常处理
错误,这也可以看出这是大势所趋

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

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

相关文章

python---列表和元组(5)

元组的相关操作 元组的创建 创建元组的时候指定初始值 元组中的元素也可以是任意类型 通过下标访问元组中的元素 下标从0开始到len-1结束 通过切片来获取元组中的一个部分 使用for循环来遍历元组 使用in 判定元素是否存在 使用index查找元素下标 使用来拼接两个元组 元…

2023年互联网Java面试复习大纲:ZK+Redis+MySQL+Java基础+架构

多数的公司总体上面试都是以自我介绍项目介绍项目细节/难点提问基础知识点考核算法题这个流程下来的。有些公司可能还会问几个实际的场景类的问题&#xff0c;这个环节阿里是必问的&#xff0c;这种问题通常是没有正确答案的&#xff0c;就看个人的理解&#xff0c;个人的积累了…

github action 基于个人项目实践

前言: DevOps 和 Jenkins 作为一名开发&#xff0c;虽然也没有经常听到 Devops &#xff08;研发和运维一体化&#xff09;这个概念&#xff0c;但日常工作中已经无处不在地用着 DevOps 工具。自研也好&#xff0c;基于开源项目改造也好&#xff0c;互联网公司基本都会有自已的…

Django-搭建sysinfo获取系统信息

文章目录 前言一、项目搭建二、主机信息监控三、Celery定时任务和异步任务 前言 使用Django&#xff0c;搭建sysinfo&#xff0c;Linux中,sysinfo是用来获取系统相关信息的结构体 本篇基于&#xff1a;https://github.com/hypersport/sysinfo#readme项目借鉴路径: https://gi…

基于开源大模型Vicuna-13B构建私有制库问答系统

本教程专注在怎么使用已经开源的模型和项目&#xff0c;构建一个可以私有化部署的问答知识库&#xff0c;而且整体效果要有所保障。 主要工作包括&#xff1a; 选择基础模型&#xff0c;openAI&#xff0c;claude 这些商用的&#xff0c;或者其他的开源的&#xff0c;这次我们…

中国金融,如何向科技要答案?

一个科技初创公司&#xff0c;能否凭借科创成果及时获得信贷准入&#xff1f; 一个农民兄弟能否在春播时&#xff0c;获得精准的无抵押贷款&#xff1b;秋收时&#xff0c;通过银行App找到性价比最高的买家&#xff1f; 一家企业&#xff0c;能否通过其生产及交易信息获取线上融…

React diff的原理是什么

一、是什么 跟Vue一致&#xff0c;React通过引入Virtual DOM的概念&#xff0c;极大地避免无效的Dom操作&#xff0c;使我们的页面的构建效率提到了极大的提升 而diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处 传统diff算法通过循环递归对节点进行依…

WIFI中的频段、信道、信道带宽

一、波长、波速与频率 波长波速/频率 “波速”由“介质”决定。 “频率”由“波源”决定。 “波长”由“介质”(波速V)、“波源”(频率f)共同决定。&#xff08;λV/f&#xff09; 波长&#xff08;wavelength&#xff09;&#xff1a; 指波在一个振动周期内传播的距离。也就…

Flutter自定义系列之折线波动图,心率图,价格走势图

随着前两篇文章的学习&#xff0c;我今天继续给大家演示下简单的自定义之折线波动图&#xff0c;心率图&#xff0c;价格走势图。 这里&#xff0c;我们创建一个自定义的StatefulWidget&#xff0c;用于显示动态的价格线。 我们将使用CustomPaint和CustomPainter来绘制价格线…

英伟达开发板学习系列---国产【Jetson Xavier NX】系统安装及基础配置

1. 前言 最近新买了Jetson Xavier NX, 和之前英伟达原厂的NX的区别在于国产Jetson Xavier NX 是核心板使用的是英伟达的&#xff0c;扩展板是国产的。具体详情如下&#xff1a; 官方NX和国产NX详情区别 2. 设置系统从固态硬盘启动 官方NX出厂是直接将SD卡&#xff08;64/12…

51单片机“密码锁”代码详解

注&#xff1a;此代码一经过验证&#xff0c;读者不必怀疑其正确性&#xff0c;如果烧录进去没有反应&#xff0c;请自行检查引脚端口配置&#xff0c;以及仔细分析代码实现原理。倘若能静下心来分析代码&#xff0c;一定能受益匪浅。 废话不多说&#xff0c;&#xff0c;直接…

网络系统安全——MS15_034漏洞利用与安全加固

Kali 192.168.124.162 Windows server 2008 192.168.124.169 检查2008服务器的IIS网站是否正常&#xff0c;进入2008服务器&#xff0c;使用ie浏览器访问本机地址 切换到kali&#xff0c;使用命令ping来测试他们的连通性 然后使用使用命令curl测试&#xff0c;测试&#x…

【每日挠头算法题(8)】最后一个单词的长度|重新排列字符串

文章目录 一、最后一个单词的长度思路1&#xff1a;从后往前遍历具体代码如下&#xff1a; 思路2&#xff1a;具体代码如下&#xff1a; 二、重新排列字符串思路具体代码如下&#xff1a; 一、最后一个单词的长度 点我直达~ 思路1&#xff1a;从后往前遍历 从后往前遍历&…

动态规划I (45、55、62、63)

按顺序刷确实效率太低了&#xff0c;今天开始要按顺序的同时也按标题来了&#xff0c;全面加油&#xff01;这种应该以后会更多直接总结题解了&#xff0c;自我学习用&#xff0c;全靠大佬&#xff0c;贴贴&#xff01;&#xff01;含45、55、62、63 CP55 跳跃游戏 题目描述&…

优雅草蜻蜓T系统·专业版服务端以及后台部署说明-完整步骤-语音会议室支持多人语音,屏幕分享,导航配置,会议管理,会员管理

蜻蜓T系统专业版服务端以及后台部署 1&#xff0c;解压文件和基础环境配置 将源码用git工具克隆到/www/wwwroot git clone git地址 或者是由优雅草发送的商业源码文件包直接进行解压 ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;…

使用pycharm入门python的一些注意点

今儿在帮别人跑一段python代码&#xff0c;实际上我对python并不熟悉&#xff0c;只能边摸索边尝试。选择了pycharm这个工具。 一.怎么安装python使用的库文件 能用来安装python的库文件的&#xff0c;有很多种办法&#xff0c;这里只介绍pip和pip3。因为pip和pip3的优势是能…

JavaEE(系列21) -- 传输层协议UDP 和 TCP

目录 1. 应用层和传输层的联系 2. UDP协议 2.1 UDP简介 2.2 UDP格式 2.2.1 目的端口和源端口 2.2.2 报文长度 2.2.3 校验和 3. TCP协议 3.1 TCP简介 3.2 TCP格式 3.2.1 数据偏移和选项(option) 3.2.2 保留项 3.2.3 6位控制位 3.2.4 32位序号和32位确认序号…

R语言 tidyverse系列学习笔记(系列4)PlantGrowth - percentage table

本篇学习数据分析&#xff0c; Excel 表格制作 Task&#xff1a; 创建一个 行 百分比 表格 row percentage table 先看一下 PlantGrowth 数据集 library(dplyr)data("PlantGrowth") view(PlantGrowth)给数据集新加一列 weight_cat &#xff0c;并用 case_when 自定…

深度学习pytorch实战五:基于ResNet34迁移学习的方法图像分类篇自建花数据集图像分类(5类)超详细代码

1.数据集简介 2.模型相关知识 3.split_data.py——训练集与测试集划分 4.model.py——定义ResNet34网络模型 5.train.py——加载数据集并训练&#xff0c;训练集计算损失值loss&#xff0c;测试集计算accuracy&#xff0c;保存训练好的网络参数 6.predict.py——利用训练好的网…

(三)Kafka 生产者

文章目录 1. Kafka 发送消息的主要步骤2.创建 Kafka 生产者3.发送消息到 Kafka&#xff08;1&#xff09;发送并忘记&#xff08;2&#xff09;同步发送&#xff08;3&#xff09;异步发送 4.生产者配置&#xff08;1&#xff09;client.id&#xff08;2&#xff09;ack&#x…