初识C++之异常

news2024/11/28 8:39:27

目录

一、C中的常用处理错误方式

二、C++异常的概念

1. throw

2. catch

3. try

 三、异常的使用

1. 异常的抛出和捕获

1.1 异常的抛出和匹配原则

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

四、异常体系

1. 自定义异常体系

2. C++中的异常体系

五、 异常安全

六、异常规范

1. throw()标识异常

2. noexcept关键字

 七、异常的优缺点

1. 优点

2. 缺点


一、C中的常用处理错误方式

在C中,如果我们的程序出现错误,通常会有两种处理方法。

(1)终止程序

例如使用assert来终止程序。但是这种终止方式是直接终止程序,用户难以接受。

(2)返回错误码

有时当出现错误时,也会通过返回错误码的方式处理错误。但是这种方式返回的只是错误码,具体的错误需要用户自己去查询。系统的很多库接口就是使用这种方式。

二、C++异常的概念

C的传统的两种处理错误的方式都是不太好的。所以,在C++中又提出了一种新的错误处理方式,即异常。当一个函数发现一个自己无法处理的错误时就可以抛出异常,让函数的直接或间接的调用者处理这个错误。

异常通常由如下几个部分组成:

1. throw

当问题出现时,程序会抛出一个异常,这个动作需要使用throw关键字来抛出。

2. catch

这个部分一般放在想要处理问题的地方,用于捕获异常可以有多个catch进行捕获

3. try

try块中的代码被标识为将激活的特定异常。即try块里面的代码就是可能出现错误的代码。它后面通常跟着一个或多个catch块。

如果一个块抛出异常,捕获方法通常会使用try和catch关键字。try块中放置可能抛出的异常,而try块中的代码就被称为“保护代码”

try和catch的使用格式也很简单:

 三、异常的使用

1. 异常的抛出和捕获

1.1 异常的抛出和匹配原则

(1)异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码:

例如上图就是一个异常捕获,当出现异常时,const char*类型的数据就被匹配到这个catch块中执行代码。

当然,这也就意味着可以有多个catch来捕获异常:

无论有多少个catch,在出现异常时,都只会进入与异常的类型相同的catch中。其他未执行的catch会被跳过

同时,在同一个try catch中,不允许出现相同类型的catch

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

写出如下测试程序:

在这个程序中,main函数和func函数中都有异常捕获。此时运行程序,看看这个异常会在哪里被捕获:

可以看到,这里的打印就表明了它是在func函数中被捕获的,因为func中异常捕获离出现异常的位置最近。

这里的位置最近,指的是函数的调用链最近。可以将函数的调用过程想象成一个链表,每个函数调用时都被链接起来,如下图:

当遇到异常时,现在自己内部找有没有能够匹配的catch,没有返回上一层函数找,找到就进入。没有则继续返回。

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

例如抛出一个string对象:

这个string对象如果不拷贝一份,就会在出了if的作用域,即进行跳转的时候被销毁,导致返回错误的信息。

(4)catch(...)可以捕获任意类型的异常。缺点就是难以知道出现了什么异常错误

在实际上,我们可能会碰见有多个有多个位置可能抛异常的情况,如果这些异常都在同一个函数内捕获,就需要写很多个catch,会很麻烦。因此,遇到这种情况时,就可以使用...来捕获异常。这种方式可以捕获任意类型的异常

但是这种方法无法生成一个临时变量来接收异常信息,所以无法知道出现了什么异常

因此,这种异常一般可能会用于在写了多个catch后,再在最后补一个捕获任意异常的catch,以防止出现类型不匹配的异常导致程序退出:

这个语句一般在实际中捕获异常时都会要求写,防止出现未知异常导致程序崩溃。

(5)实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配。可以抛出的派生类对象,使用基类捕获

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

(1)首先检查throw本身是否在try块内部,如果在再查找匹配的catch语句

如果有匹配的,则跳转到catch中执行代码。如果没有,则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。如果到达main函数的栈中都依然没有匹配的catch,则终止程序。

在这个程序中,就没有可以匹配的catch,因为抛出的异常是const char*,无法与char*匹配。运行该程序:

此时程序直接报错终止。

这种沿着调用链进行匹配的catch子句的过程就被称为“栈展开”

(2)当找到匹配的catch子句并处理后,会跳过后面的catch子句并继续向后执行

之所以会有捕获catch后不退出程序继续向后执行的特性,是为了应对一些特殊情况,例如下图:

在这个程序中,new了一块空间。我们知道,new出来的空间除非我们自己释放,否则只要这个程序未结束,那么这块空间就会一直被占用。如果没有catch后继续向后执行的机制,在上面的情况中,就可能会导致内存泄漏,因为delete被放在了catch后面,如果不继续执行,就可能导致出现错误。

这一机制一般是配合多个函数内可以实现多个catch,并且在出现异常后会优先进入离异常最近的catch执行。因为在实际中也可能出现如下情况:

在上面的程序中,func函数中new了一块空间,这块空间在出现异常时还未被捕获。此时func中没有可以匹配的catch,就导致异常进入main函数中被捕获。这时异常直接从出现异常的Division函数中跳转到了main函数中,没有执行func中的delete语句。这就可能导致内存泄漏。

因此,在会抛出异常的块中如果调用了其他函数,最好要在本级配备对应的可匹配的catch,以免出现上述情况。

四、异常体系

1. 自定义异常体系

在实际中,可能会遇到这样的情况:一个程序的很多地方都可能抛异常,而这些异常都要在最外层进行接收。此时就会导致外部需要频繁的接收异常,这对于最外层来讲无疑是难以接受的。

在实际中,接收异常都会有一些异常体系。这些异常体系一般是用继承实现的。通过继承的方式,让不同的子类接收某一类型的异常信息。通过这一方式,在最外部的catch就只需捕获父类即可。

在这里,为了模拟出这一场景,所以写了一个简单的程序,用取模来模拟出现异常的情况,然后让main函数接收捕获父类:

class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg), _id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

protected:
	string _errmsg;
	int _id;
};

class SqlException : public Exception
{
public:
	SqlException(const string& errmsg, int id, const string& sql)
		:Exception(errmsg, id), _sql(sql)
	{}

	virtual string what() const
	{
		string str = "SqlException:";
		str += _errmsg;
		str += "->";
		str += _sql;

		return str;
	}

private:
	const string _sql;
};

class CacheException : public Exception
{
public:
	CacheException(const string& errmsg, int id)
		:Exception(errmsg, id)
	{}

	virtual string what() const
	{
		string str = "CacheException:";
		str += _errmsg;
		return str;
	}
};

void SQLMgr()
{
	srand(time(0));
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	}
}

void CacheMgr()
{
	srand(time(0));
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	}
	else if(rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	}

	SQLMgr();
}

int main()
{
	while (true)
	{
		Sleep(1000);//<widnows.h>头文件提供的休眠函数,休眠1ss

		try
		{
			CacheMgr();
		}
		catch (const Exception& e)//捕获父对象
		{
			cout << e.what() << endl;//多态
		}
		catch (...)
		{
			cout << "UnKown Exception" << endl;
		}
	}

	return 0;
}

运行该程序:

 此时,该程序就将错误信息打印了出来。

2. C++中的异常体系

C++中也是提供了异常体系的,名字叫做“exception”。它就是通过子类继承父类的方式来实现异常抛出的

但是这一异常体系并不好用,一般来讲,很多公司都会有一套自己的异常体系,而不会使用这个库中提供的异常体系。所以这里就不过多赘述了。

如果大家用库中的异常体系,直接在catch中捕获exception类即可:

要打印异常信息,则使用这个类中的what()函数。

五、 异常安全

在使用异常时,最好不要在以下几个地方抛异常:

(1)构造函数完成对象的构造和实例化时,最好不要在构造函数中抛出异常,这可能导致对象不完整或没有完全初始化。

(2)析构函数主要完成资源的清理,最好不要再析构函数内抛出异常,否则可能导致资源泄漏。

(3)C++中的异常经常会导致资源泄漏的问题,比如在new和delete中抛出了异常,导致内存泄漏;在lock和unlock之间抛出了异常,导致死锁。在C++中,一般采用RAII来解决这些问题。RAII在这里先不讲解。

六、异常规范

在实际中,我们可能不止会调用自己写的函数,也可能调用他人写的函数。虽然我们可以知道自己写的函数中是否会抛异常,但是难以知道他人的函数中是否会抛出异常。为了解决这一问题,就有了C++的异常规范。

1. throw()标识异常

在C++98中,提供了通过throw来标识该函数可能抛出哪些类型的异常:

上面这个函数就表示该函数可能会抛出一个string类型的异常。如果这个throw中什么都没有,就说明这个函数不会抛异常。

但是这种方法有一个很明显的弊端,那就是这个函数内部可能会调用库中的函数或者其他人写的函数。这就会导致如果要在这个throw中标识该函数可能抛出的异常,就要去查看这个函数中调用的其他函数可能抛出的异常。无疑,这项工作时比较繁琐的。并且这个异常标准并不是强制要求的,因此这个标准也就没有被广大的C++用户所接受,可以说是名存实亡。

2. noexcept关键字

由于C++98的异常规范未被接受,因此在C++11的时候,又推出了另一个异常规范,即noexcept关键字。带有这个关键字的函数就表示“不会抛出异常”。同时,这个关键字也会对函数是否抛异常进行检查

由此,如果大家在未来想要使用标明自己的函数是否会抛出异常,就可以使用noexcept关键字,尽量不要使用C++98的异常标准,只需要在遇见时可以看懂即可。

 七、异常的优缺点

1. 优点

(1)异常对象定义好后,相比错误码的方式,可以清晰准确的展示出错误的各种信息,甚至可以包含堆栈调用的信息,这样可以帮助更好的定位程序bug

(2)返回错误码的传统方式本身就有着一定的缺陷。在函数调用链中,当深层的函数返回了错误码后,我们需要层层向外返回错误,这样才能让最外层拿到这个错误码。

 (3)很多的第三方库,例如boost、glest、gmock等常用的第三方库中都包含了异常,在使用这些库时,也需要使用异常

(4)部分函数使用异常更好处理,比如构造函数这种没有返回值的函数。这些函数由于没有返回值,就难以用错误码的方式处理。当然,还有如返回值为T&这种在类模板中的函数,也难以通过返回值的方式来标识错误。

例如上面这个函数,返回错误码的方式就并不太好用。

2. 缺点

(1)异常会导致程序的执行流乱跳,非常的混乱。并且是在运行出错抛异常的时候乱跳,这可能就会导致一些难以预料的问题,比如内存泄漏。同时也会让我们在跟踪调试及分析程序的时候比较困难。

(2)异常会有一些性能的开销,但是这个开销其实并不高,基本可以忽略。

(3)C++和java等语言不同,没有垃圾回收机制,程序申请的资源需要自己管理。有了异常后就很可能导致内存泄漏、死锁等异常安全问题。要解决这一问题,需要使用RAII来处理资源的管理问题,这一方法会放在下一章“智能指针”中讲解。

(4)C++标准库的异常体系定义的不好,导致很多人都不太喜欢用或者说用的比较混乱。

(5)异常不能随意抛,否则会让最外层接收异常的用户非常难受。因此异常一般又两个规范:1是抛出的异常类型都继承自一个基类;而是函数是否抛异常,最好用noexcept标识。

总的来讲,异常是利大于弊的,因此在大家未来的实际工作中还是比较推荐使用异常以帮助定位错误的。

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

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

相关文章

Unity 热更新基础HybridCLR:Windows平台使用(HybridCLR手记二)

项目是根据官网的示例工程进行修改的,版本参数如下&#xff1a; unity&#xff1a;2021.2.20 wolong&#xff1a;v2.1.0 il2cpp_plus:v2021_2.1.0 ------------------------------------------------------------- 1、安装&#xff1a;参考&#xff1a;第一篇文章Unity 热…

pytest - Getting Start

前言 项目开发中有很多的功能&#xff0c;通常开发人员需要对自己编写的代码进行自测&#xff0c;除了借助postman等工具进行测试外&#xff0c;还需要编写单元测试对开发的代码进行测试&#xff0c;通过单元测试来判断代码是否能够实现需求&#xff0c;本文介绍的pytest模块是…

虚幻图文笔记:Substance Painter通过USD格式导入UE5的工作流

什么是USD格式 USD即Universal Scene Description&#xff0c;是著名的Pixar公司研发的一种开源的3D 场景说明和文件格式&#xff0c;如其名所示&#xff0c;相较于传统的FBX、Obj等3D格式&#xff0c;USD的具有更好的通用性和扩展性&#xff0c;现在已被非常多的厂商和平台所…

使用树莓派(zero2w + Camera Module 3 支持自动对焦 1200 万像素)拍照

拍照硬件 Raspberry Pi Camera Module 3Raspberry Pi Zero 2 W 1. 注意排线连接方向 2. 烧录系统 3. 进行系统更新需要联网&#xff0c;注意只支持最新的bullseye系统。每条指令大概需要10 分钟。 sudo apt-get update -y sudo apt-get upgrade -y4. 拍摄一张全像素的JPEG图像…

自己做小程序开个社区团购可行吗?

在如今的社交化时代&#xff0c;随着社区经济的发展&#xff0c;越来越多的人开始探索社区团购的商业模式。而随着小程序的普及&#xff0c;自己开发一个社区团购小程序也成为了一种可能。但是&#xff0c;自己做小程序开个社区团购真的可行吗&#xff1f;我们来一起分析一下。…

Spring整合MybatisJunit单元测试

Spring整合Mybatis&Junit单元测试 1. Spring整合Mybatis【重点】1.1 思路分析问题导入1.1.1 MyBatis程序核心对象分析1.1.2 整合MyBatis 1.2 代码实现【前置工作】【第一步】导入Spring整合Mybatis依赖【第二步】创建JdbcConfig配置DataSource数据源【第三步】创建MybatisC…

【ARMv8 编程】A64 内存访问指令——内存加载指令

与所有先前的 ARM 处理器一样&#xff0c;ARMv8 架构是一种加载/存储架构。这意味着没有数据处理指令直接对内存中的数据进行操作。数据必须首先被加载到寄存器中&#xff0c;修改&#xff0c;然后存储到内存中。该程序必须指定地址、要传输的数据大小以及源或目标寄存器。有额…

《使用深度神经网络对光电容积脉搏图进行归一化,以进行个体和群体比较》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一…

symfonos 1(smtp注入webshell,配合文件文件包含)

目录 扫描 SMB 提权 扫描 SMB 让我们使用SMBMAP检查这些目录的权限。 smbmap -d workgroup -H www.example.com 可能/匿名帐户可访问。 使用smb尝试连接共享网络以访问/anonymous目录。[smb://192.168.59。129/]

ETL工具 - Kettle 介绍及基本使用

一、Kettle 介绍 在介绍 Kettle 前先了解下什么是 ETL&#xff0c;ETL是 Extract-Transform-Load 的缩写&#xff0c;即数据 抽取、转换、装载 的过程&#xff0c;对于企业或行业应用来说&#xff0c;经常会遇到各种异构数据的处理、转换、迁移等操作&#xff0c;这些操作有可…

java+mysql医院住院挂号缴费病房信息管理系统

手续办理&#xff1a;办理病人入院登记&#xff1b;提供病案首页建立与打印&#xff1b;交纳预交金及日结管理&#xff0c;并打印收据凭证&#xff1b;空床查询与统计&#xff1b;查询患者的住院信息&#xff1b;打印清单&#xff1b;出入院统计。 护士工作站&#xff1a;提供病…

Java每日一练(20230429)

目录 1. 二叉树的后序遍历 &#x1f31f;&#x1f31f; 2. 删除无效的括号 &#x1f31f;&#x1f31f;&#x1f31f; 3. 合并两个有序链表 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每…

【Java笔试强训 1】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f9be;&#x1f9be;&#x1f9be; 目录 一、选择题 二、编程题 &#x1f525;组队竞…

基于虚拟同步发电机的光伏混合储能并网系统MATLAB仿真

资源地址&#xff1a; 主要模块&#xff1a; 光伏电池模型&#xff08;按照数学模型搭建&#xff09;、蓄电池储能模块、超级电容储能模块、双向DC/DC模块、LC滤波器、逆变器VSG控制模块电压电流双环控制模块、光伏MPPT控制模块、储能系统充放电控制模块。 使用MATLAB2021b及…

2023/04/24 ~ 25 刷题记录

A - Sort the Subarray 大致题义&#xff1a;Monocarp有一个包含n个整数的数组a。他决定选择两个整数l和r&#xff0c;使1< 2rn&#xff0c;然后对子数组进行排序。子数组a[1 ..]R]是数组a中包含元素a1, al1, al2&#xff0c;…的部分。&#xff0c; ar-1, ar)按非降序排列。…

【STM32】知识补充 锁相环原理与应用解析

【STM32】知识补充 锁相环原理与应用解析 概述什么是锁相环 (PPL)锁相环的基本组成锁相环的工作原理锁相环应用STM32 中锁相环的应用STM32 配置锁相环总结 概述 锁相环 (Phase-Locked Loop) 在现代电子与通信系统中, 扮演着至关重要的角色. 凭借其独特的同步和频率调整能力, 锁…

大数据Doris(七):BE扩缩容

文章目录 BE扩缩容 一、BE扩容(创建BE与FE关系) 二、BE缩容 三、BE扩缩容注意问题

【操作系统复习】第5章 存储器管理 2

分页存储管理方式 页号P ◆12-31位&#xff1a;20位 ◆地址空间最多允许有1M&#xff08;2 20&#xff09;页 位移量W&#xff08;页内地址&#xff09; ◆0-11&#xff1a;12位 ◆每页大小为4KB &#xff08;2 12&#xff09; 对某特定机器&#xff0c;地址结构是一…

LangChain 2 ONgDB:大模型+知识图谱实现领域知识问答

LangChain 2 ONgDB&#xff1a;大模型知识图谱实现领域知识问答 LangChain 2 ONgDB&#xff1a;大模型知识图谱实现领域知识问答系统截图LangChain代理流程 Here’s the table of contents: LangChain 2 ONgDB&#xff1a;大模型知识图谱实现领域知识问答 LangChain 是一种 LL…

社区团购小程序怎么做,全流程解析

在当前的电商市场中&#xff0c;社区团购已经成为了一股强劲的力量。社区团购小程序作为社区团购的重要组成部分&#xff0c;已经成为了商家和消费者不可或缺的工具。社区团购小程序以其方便、快捷、实惠的特点&#xff0c;受到越来越多的用户的青睐&#xff0c;成为了电商市场…