【C++】异常exception

news2024/11/18 6:02:24

目录

一.C语言错误处理方式

1.assert(断言)

2.返回/设置错误码

二.C++异常的概念与使用

1.异常的概念

2.异常的使用

三.自定义异常体系MyException

四.异常的重新抛出

五.异常安全问题

六.异常规范

七.异常的优缺点对比


一.C语言错误处理方式

一个C语言程序, 在运行期间出现问题(除零错误, 非法访问空指针...), 传统的处理这些问题的机制:

1.assert(断言)

使用需要包头文件assert.h, 一但进程运行期间出现错误直接终止程序, 并且只能在debug模式下起到作用, release版本将会直接优化掉assert

2.返回/设置错误码

编译器自动设置一个全局变量errno来存放错误码, 并且需要手动查找几号错误对应的错误信息, 例如调用strerror()等函数, strerror(errno), 或者使用perror()函数

二.C++异常的概念与使用

1.异常的概念

C++属于面向对象的语言, 并且设计了一套完整的异常体系, 用于处理程序出现错误出现异常的情况

相较于C语言的assert或错误码而言, 使用异常体系判断错误信息更加便捷, 规范, 完整, 是面向对象语言判断错误的首选方式

异常被抛出并且捕获之后, 进程不会终止, 而是会在捕获处理结束之后继续执行

异常三板斧:

"捕" -- try

"抛" -- throw 

"获" -- catch

写在try代码块中的函数被捕捉到, 意为要检查是否有异常抛出

一但try代码块中有throw抛出异常, 直接跳转到catch

将抛出内容以传参的形式传到catch中, 再由catch获取到抛出的异常, 在catch代码块内进行打印说明

以上流程结束后, 继续执行catch以下的逻辑

注意点: 一但有异常抛出必须被获取, 否则程序强行终止

void func()
{
    throw ...;//抛
}
try//捕
{
    func()
}
catch(参数)//获
{}
catch(参数)//获
{}
catch(...)//获
{}

2.异常的使用

重点说在前: 在使用异常体系时, 一但有异常抛出必须被获取, 否则程序强行终止

异常体系使用准则

1.异常通过抛出对象引发, 并且基于抛出对象类型来确定跳转到哪一个catch, 跳转到catch的过程就类似于传参的过程

2.如果有多层异常捕捉, 采用栈展开的形式, 自动匹配距离最近的上一层栈帧中的catch, 若上一层没有匹配的, 则继续再向上展开, 直到main函数, 若main中也没匹配, 直接进程终止

3.抛出异常对象之后, 会自动生成一个该对象的拷贝, 为了防止异常对象是一个临时对象, 拷贝的临时对象在被catch之后销毁, 类似函数传值返回

4.catch(...)可以捕捉任意类型的异常, 这是异常体系中获取异常的最后一道防线, 因为在实际使用时throw的对象是多样的, 如果异常因类型不匹配而没有catch进程则被强制终止, 后面会统一throw对象的标准, 但为了以防万一, 每一次try...catch最终都一定要加上最后防线

5.throw和catch的类型并不都是完全匹配, 唯一特例则是可以throw派生类对象,使用基类类型catch, 再结合多态之后, 实际价值极高

异常体系执行流程

想要throw抛出异常, 必须先对函数进行try捕捉, 执行到函数时, 创建并进入函数栈帧, 执行函数内容

在执行期间, 1. 如果throw抛出异常, throw之后的内容不被执行, 直接跳转至对应严格匹配的catch处

catch捕捉块内逻辑开始执行, 执行结束之后, 整套异常捕获流程结束, 开始catch之后的代码逻辑

2. 如果没有出现异常, 则函数返回后, 继续执行try中, 之后的逻辑

注: 以上如果throw异常, 有两处代码逻辑被跳过, 代码块中: 1. try之后的逻辑被跳过 2. throw之后的逻辑被跳过

以上流程中, throw就必须被catch, 否则程序终止, 而catch(...)则是守护这一原则的最后底线, 它会捕捉所有类型的异常对象

#include<iostream>
#include<string>
using namespace std;

void _func(int a, int b)
{
	if (b == 0)
	{
		throw "_func除零错误";
	}
	cout << a / b << endl;
}

void func()
{
	try
	{
		_func(10, 0);
	}
	catch (const string& str)
	{
		cout << "func捕获 const string& str : " << str << endl;
	}
	catch (const char* str)//避免throw传值返回对象销毁, 加const延长临时对象生命周期, 下面同理, 如果不加const, 异常就会被下面最合适的catch捕捉
	{
		cout << "func捕获 const char* str: " << str << endl;
	}
	catch (...)
	{
		cout << "func捕获 未知错误" << endl;
	}
}


int main()
{
	try
	{
		func();
	}
	catch (int x)
	{
		cout << "main捕获 int x: " << x << endl;
	}
	catch (const char* str)
	{
		cout << "main捕获 const char* str: " << str << endl;
	}
	catch (const string& str)
	{
		cout << "main捕获 const string& str : " << str << endl;
	}
	catch (...)
	{
		cout << "main捕获 未知错误" << endl;
	}

	//...
	//接下来的逻辑
	cout << "以上异常测试结束\n";
	return 0;
}

 

以上代码中还有一个值得一提的是: 异常在throw后匹配对应catch时, 对于抛出的对象不会隐式类型转换, 例如string str = "除零错误"; 在异常体系中是不会这么做的

异常体系只是单纯且严格的找catch中类型匹配的那一个, 例如以上代码"除零错误"这是一个字符串, 而它只能匹配const char* str, 字符指针, 并不能匹配const string& str

这并不是选择一个最优的catch匹配而得到的结果, 因为如果没有写const char* str类型的catch, 则直接判定没有对应的catch, 也就没有获取到而直接导致进程终止, 这块博主是自己做了测试的

三.自定义异常体系MyException

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<ctime>
#include<cstdlib>
#include<windows.h>
using namespace std;

//自定义异常体系
//整体思想: 派生类抛出, 基类捕捉
//原理: 使用基类捕捉从而达成多态调用, 详细展示出问题所在模块的详细信息 

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

	virtual string what() const
	{
		return _errmsg;
	}

protected:
	string _errmsg;
	int _id;
};

class MyException1 :public MyException
{
public:
	MyException1(const string& errmsg, int id, const string& errmsg1)
		:MyException(errmsg, id),
		_errmsg1(errmsg1)
	{}

	virtual string what() const
	{
		string resStr = "MyException1: ";
		resStr += _errmsg;
		resStr += "-->";
		resStr += _errmsg1;
		resStr += "-->";
		resStr += to_string(_id);
		return resStr;
	}

protected:
	string _errmsg1;
};

class MyException2 :public MyException
{
public:
	MyException2(const string& errmsg, int id, const string& errmsg1)
		:MyException(errmsg, id),
		_errmsg1(errmsg1)
	{}

	virtual string what() const
	{
		string resStr = "MyException2: ";
		resStr += _errmsg;
		resStr += "-->";
		resStr += _errmsg1;
		resStr += "-->";
		resStr += to_string(_id);
		return resStr;
	}

protected:
	string _errmsg1;
};

class MyException3 :public MyException
{
public:
	MyException3(const string& errmsg, int id, const string& errmsg1)
		:MyException(errmsg, id),
		_errmsg1(errmsg1)
	{}

	virtual string what() const
	{
		string resStr = "MyException3: ";
		resStr += _errmsg;
		resStr += "-->";
		resStr += _errmsg1;
		resStr += "-->";
		resStr += to_string(_id);
		return resStr;
	}

protected:
	string _errmsg1;
};

void exception_test_3()
{
	int a = rand() % 10 + 1;
	if (a == 3 || a == 8)
	{
		throw MyException3("error", a, "MyException3");
	}
	cout << "执行成功\n";
}

void exception_test_2()
{
	int a = rand() % 15 + 1;
	if (a == 8 || a == 12)
	{
		throw MyException2("error", a, "MyException2");
	}
	else
	{
		exception_test_3();
	}
}

void exception_test_1()
{
	int a = rand() % 10 + 1;
	if (a == 2 || a == 3)
	{
		throw MyException1("error", a, "MyException1");
	}
	else
	{
		exception_test_2();
	}
}

int main()
{
	srand((unsigned int)time(nullptr));
	while (1)
	{
		try
		{
			exception_test_1();
		}
		catch (const MyException& e)
		{
			cout << e.what() << endl;
		}
		catch (...)
		{
			cout << "未知错误\n";
		}
		Sleep(1000);
	}
	return 0;
}

 

四.异常的重新抛出

//异常重新抛出
//如果整套逻辑中出现3号错误, 则要重试三次再抛出异常
void _Server()
{
	int x = rand() % 3 + 1;
	if (x == 3 || x == 1)
	{
		throw x;
	}
	cout << "本次无异常\n";
}

void Server()
{
	//处理一些服务, 服务底层调用_Server
	//为了能够重新运行三次, 在这里截获异常, 筛选后不符合条件就重新抛出
	//此外3号异常重试三次以后也要抛出
	int n = 3;
	while (n--)
	{
		try
		{
			_Server();
			break;
		}
		catch (int errnum)
		{
			if (errnum == 3 && n > 0)//是3号异常且还未重试3次,继续重试
			{
				cout << "本次为3号异常, 正在重试...\n";
				continue;
			}
			else
			{
				//异常重新抛出!
				if (errnum == 3)
				{
					int a = 10;
				}
				throw errnum;
			}
		}
		catch (...)
		{
			cout << "未知异常\n";
		}
	}
	
}

int main()
{
	srand((unsigned int)time(nullptr));
	while (1)
	{
		try
		{
			Server();
		}
		catch (int errnum)
		{
			cout << errnum << "号异常已捕获" << endl;
		}
		catch (...)
		{
			cout << "未知异常\n";
		}
		Sleep(1000);
	}
	return 0;
}

五.异常安全问题

1.构造函数完成对象构造与初始化时, 不要在构造函数中抛出异常, 会导致对象不完整或没有完全初始化

2.析构函数在释放资源时, 不要在其中抛出异常, 会导致对象资源释放不完全, 导致内存泄漏问题

3.在异常体系中, 如果出现new/delete, 若在new之后出现异常, 则由于异常捕获的跳转, 则无法释放资源导致内存泄漏问题

4.在lock与unlock之间若抛异常则易发生死锁

(3)(4)点问题在C++中使用RAII(资源托管)思想, 智能指针可以完美解决

六.异常规范

//异常规范

//表示func1函数会抛出int,float,bool这三种类型范围之内的异常
void func1() throw(int, float, bool);
//表示func2函数不会抛出异常(C++98)
void func2() throw();
//表示func4只会抛出int类型异常
void func4() throw(int);

//以上在C++11不再推荐使用

//表示func3函数不会抛出异常(C++11)
void func3() noexcept;

注: 这些规范编译器不会强制要求, 因为要兼容C语言, C语言没有异常体系

七.异常的优缺点对比

优点:

1.可以清晰准确展示错误信息, 更好定位到bug的位置

2.与传统的返回错误码对比, 不用层层返回错误信息, 而是直接跳转到捕获位置一目了然

3.使用异常更好处理错误, 如传统解决方式, assert只会在debug模式下生效, 并且一但断言成功程序直接终止, 返回码也不是在所有函数下都能返回的, 有可能返回信息被占用

缺点:

1.导致执行流不稳定, 若发生异常会直接跳转, 不方便调试, 例如: 打断点调试有可能直接在断点之前就抛出异常从而跳转, 无法走到指定断点位置

2.额外性能开销, 但在当代计算机的计算能力来看可以忽略不计

3.容易导致内存泄漏, 异常安全问题, 需要借助C++智能指针RAII思想来解决

4.标准库的异常定义较为混乱, 通常情况下使用自己实现的异常体系

5.由于兼容C语言, 导致异常规范犹如道德一般, 是否遵守并无法强制规定, 但作为一名合格程序员, 必须遵守异常规范! 若不遵守规范会诱发更多麻烦问题

但在C++11只推荐使用, 如果没有异常则使用noexcept标注即可.

总体而言: 异常的使用利大于弊

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

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

相关文章

回归分析与相关分析的区别和联系

在本节中&#xff0c;我们将首先讨论相关性分析&#xff0c;它用于量化两个连续变量之间的关联&#xff08;例如&#xff0c;独立变量与因变量之间或两个独立变量之间&#xff09;。 最近我们被客户要求撰写关于回归分析与相关分析的研究报告&#xff0c;包括一些图形和统计输…

软件测试经验与教训

下面精选出10条&#xff0c;和大家分享。 01 测试人员是项目的前灯 一个项目就像是一次陆上旅行。有些项目很简单、很平常&#xff0c;就像是大白天开车去商店买东西。但是大多数值得开发的项目更像是夜间在山里开越野卡车&#xff0c;这些项目需要前灯&#xff0c;而测试员要照…

直播带货行业如何入局?先了解一下直播商城源码吧

直播行业的爆火已经持续了多个年头&#xff0c;直到今天&#xff0c;在人们的生活中依然有着举足轻重的地位&#xff0c;它通过多元化的方案为许多行业带来了新的思路&#xff0c;特别是与传统商业所结合的“直播电商”、“直播商城”的卖货新形式&#xff0c;让多方因此而受益…

数理化解题研究杂志社数理化解题研究编辑部2022年第30期目录

教学改革探索 信息技术下中职数学“翻转课堂”教学创新策略研究 李宇仙; 2-4《数理化解题研究》投稿&#xff1a;cn7kantougao163.com 基于高中数学核心素养的错题讲评课之探索与实践 施浩妹; 17-20 高中数学“问题导学”模式的实践研究 吴金桥; 21-23 立于神而…

【测试沉思录】21. 如何用 JMeter 编写性能测试脚本?

作者&#xff1a;宋赟 编辑&#xff1a;毕小烦 Apache JMeter 应该是应用最广泛的性能测试工具。怎么用 JMeter 编写性能测试脚本&#xff1f; 1. 编写 HTTP 性能测试脚本 STEP 1. 添加 HTTP 请求 STEP 2. 了解配置信息 HTTP 请求各项信息说明&#xff08;以 JMeter 5.1 为例…

【强化学习论文合集】十.2018智能体和多智能体系统国际联合会议论文(AAMAS2018)

强化学习(Reinforcement Learning, RL),又称再励学习、评价学习或增强学习,是机器学习的范式和方法论之一,用于描述和解决智能体(agent)在与环境的交互过程中通过学习策略以达成回报最大化或实现特定目标的问题。 本专栏整理了近几年国际顶级会议中,涉及强化学习(Rein…

十四、SpringBoot-自动装配原理

十四、SpringBoot-自动装配原理 SpringBoot与Spring比较起来&#xff0c;优化的点主要有&#xff1a; 自动配置&#xff1a;是一个运行时&#xff08;应用程序启动时&#xff09;的过程&#xff0c;考虑了众多因素&#xff0c;才决定Spring配置应该用哪个&#xff0c;不该用哪…

软件测试基础丨测试工程师之间要善于发现闪光点——测试理念篇

测试理念有多种&#xff0c;有一些理念&#xff0c;深藏于我的心中&#xff0c; 而这些理念&#xff0c;您或许偶尔想到&#xff0c;却没有说出&#xff0c;或许您感受到了&#xff0c;却因为工作生活的忙碌&#xff0c;没有将其背后的含义想具体&#xff0c; 在此我非常愿意和…

零基础小白hadoop分布式集群环境搭建(超详细)

搭建集群所需要安装包 虚拟机、ubuntu镜像文件、jdk安装包、hadoop安装包 百度云盘地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1ejVamlrlyoWtJRo1QQqlsA提取码&#xff1a;fcqm 本文的环境是两台windows笔记本&#xff0c;在每台笔记本上安装一个虚拟机&…

超详细的水果FL Studio21最新版更新全功能详细介绍!80项更新与改进!

万众期待的 FL Studio 21 版本将于正式发布上线&#xff0c;目前在紧锣密鼓的安排上线中&#xff0c;届时所有购买正版 FL Studio 的用户&#xff0c;都可以免费升级到21版&#xff01;按照惯例&#xff0c;本次新版也会增加全新插件&#xff0c;来帮助大家更好地创作。今天先给…

SMART原则介绍

一、SMART原则简介 什么是SMART原则? SMART原则(S=Specific、M=Measurable、A=Attainable、R=Relevant、T=Time-bound)是为了利于员工更加明确高效地工作,更是为了管理者将来对员工实施绩效考核提供了考核目标和考核标准,使考核更加科学化、规范化,更能保证考核的公正、…

五万字详解“GoF”的23种设计模式

大家好&#xff0c;我是栗筝i&#xff0c;近期我总结梳理了 “GoF”的 23 种设计模式&#xff0c;并使用 Java 对每种设计模式都进行了伪代码与 Demo 实现&#xff0c;并总结了每种设计模式的应用场景&#xff0c;优缺点&#xff0c;UML图等相关内容&#xff0c;字/词数达到了5…

Java中的String

/*** 关于java.lang.String类* 1、String表示字符串类型&#xff0c;属于引用数据类型&#xff0c;不属于基本数据类型* 2、在java中用双引号括起来的都是String对象* 3、java中规定&#xff0c;字符串是不可变的* 4、字符串存储在方法区的字符串常量池当中*/ …

单例模式(python)

一、模式定义 1. 单例模式(Singleton Pattern)&#xff1a;确保某一个类只有一个实例&#xff0c;而且自行实例化并向整个系统提供这个实例&#xff0c;这个类称为单例类&#xff0c;它提供全局访问的方法。 2. 单例模式的要点有三个&#xff1a; 某个类只能有一个实例 必须自…

[激光原理与应用-41]:《光电检测技术-8》- 白光干涉仪

目录 第1章 白光干涉仪概述 第2章 常见干涉仪 2.1 激光量块干涉仪 2.2 白光干涉测量表面形貌的系统 第1章 白光干涉仪概述 用于光在两个不同表面反射后形成的干涉条纹进行分析的设备。 干涉仪是一种对光在两个不同表面反射后形成的干涉条纹进行分析的仪器。 其基本原理就…

SpringColud——Ribbon(负载均衡)Hystrix(熔断器)

目录 1、Ribbon 1.1、什么是Ribbon&#xff08;负载均衡&#xff09; 1.2、创建两个user-service实例 1.3、开启负载均衡 2、Histrix&#xff08;熔断器&#xff09; 2.1、什么是Histrix 2.2、雪崩问题 2.3、服务降级 2.4、开启熔断 2.5、编写降级逻辑 2.6、编写降级…

顶象App加固——助力微投证券融入IOS生态

过去十年里&#xff0c;App几乎重新定义了互联网&#xff0c;如今所有人的智能手机里都有着无数个App。 以App store 为例。最新数据显示&#xff0c;当前61个国家/地区在 App Store 现存的App总数为29,085,727。其中虽包含重复的App&#xff0c;但也可见在不同国家/地区&…

毕业设计 基于STM32单片机的老人防摔倒报警系统 - 物联网 嵌入式

文章目录0 前言1 整体设计2 硬件电路3 软件设计4 跌倒检测算法5 关键代码6 最后0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉…

Partition of an interval

In mathematics, a partition of an interval [a, b] on the real line is a finite sequence x0, x1, x2, …, xn of real numbers such that a x0 < x1 < x2 < … < xn b. In other terms, a partition of a compact interval I is a strictly increasing seq…

《小白WEB安全入门》01. 扫盲篇

扫盲篇基础知识什么是WEB什么是前端什么是后端什么是数据库什么是协议什么是WEB安全什么是服务器什么是IP地址、端口什么是局域网、广域网、内网、外网什么是URL什么是MAC地址什么是&#xff08;端口&#xff09;映射什么是域名、DNS什么是网卡、网关什么是IPv4/IPv6什么是Linu…