【C++】异常,你了解了吗?

news2024/12/27 13:34:52

在之前的C语言处理错误时,会通过assert和错误码的方式来解决,这导致了发生错误就会直接把程序关闭,或者当调用链较长时,就会一层一层的去确定错误码,降低效率,所以c++针对处理错误,出现了异常,一起来学习!

 

目录

1.C++异常的出现

C语言中处理错误的方式

C++异常概念

2.异常的抛出和匹配原则   

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

4.异常的特殊类型匹配(异常体系)

5.异常安全

6.异常的优缺点

总之,利大于弊


1.C++异常的出现

C语言中处理错误的方式

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

C++异常概念

  异常是一种处理错误的方式,当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的
直接或间接的调用者处理这个错误。
throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的。
catch: 在您想要处理问题的地方,通过异常处理程序捕获异常.catch 关键字用于捕获异
常,可以有多个catch进行捕获
try: try 块中的代码标识将被激活的特定异常,它后面通常跟着一个或多个 catch 块。  
如果有一个块抛出一个异常,捕获异常的方法会使用 try catch 关键字。try 块中放置可能抛
出异常的代码,try 块中的代码被称为保护代码。
如果正常执行,没有发生异常,那就和try-catch没有关系了,就会正常进行。
举个例子:
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";// throw  const char*对象
	else
		return ((double)a / (double)b);
}
void Func()
{

	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl; 
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
		// 记录日志,进行统一处理
	}
	catch (int errid)
	{
		cout << errid << endl;
	}

	return 0;
}
可以看到,try中调用func函数,若调用这个函数过程中有异常抛出,就会直接跳到try下面的catch来捕获异常。那么异常具体要注意些什么,它的规则又是什么呢?

 2.异常的抛出和匹配原则   

1. 异常是通过抛出对象而引发的,该对象的类型决定了应该激活哪个catch的处理代码。
  
throw抛出的异常,必须要有类型匹配的catch所对应,如果没有类型匹配的catch,会直接报错,终止程序!
2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。

当除数为0时,抛出异常,他会先找当前作用域的catch,如果没有,就会返回调用链中调用它的上一个中去找catch,如果没有catch,就会直接报错,终止程序。

 

同一位置可以有多个类型不用的catch,但是同一位置不可以有相同类型的catch,相同类型的catch必须分布在不同的位置(可以看作是提前catch):

double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
		throw "Division by zero condition!";// throw  const char*对象
	else
		return ((double)a / (double)b);
}
void Func()
{
    try
  {

	int len, time;
	cin >> len >> time;
	cout << Division(len, time) << endl; 
  }

  catch (const char* errmsg) //const char*类型
  {
		cout << errmsg << endl;
		// 记录日志,进行统一处理
  }

}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg) //const char*类型
	{
		cout << errmsg << endl;
		// 记录日志,进行统一处理
	}
	catch (int errid)  //int 类型
	{
		cout << errid << endl;
	}
	return 0;
}
throw抛出的可以是异常对象:
3. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象,所以会生成一个拷贝对象,这个拷贝的临时对象会在被catch以后销毁。(这里的处理类似于函数的传值返回)

 当然,编译器会自动识别为将亡值,然后调用移动构造,直接构造。

4. catch(...)可以捕获任意类型的异常,问题是不知道异常错误是什么。
如果出现这种情况:new出的对象因为catch而导致执行顺序混乱,没有释放就会导致内存泄漏。
double Division(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		string s("Division by zero condition!");
		throw s;
	}
	else
		return ((double)a / (double)b);
}
void Func()
{
    //new的资源需要释放
	int* p1 = new int[10];

	try
	{
		int len, time;
		cin >> len >> time;
		cout << Division(len, time) << endl; // throw  const char*对象
		// func()   // throw double对象
	}

	catch (...)  // 表示可以捕获任意类型的异常
	{
		//cout << errmsg << endl;
		cout << "delete" << p1 << endl;
		delete[] p1;

		throw;  // 重新抛出,捕获到什么就抛出什么
	}
	//catch (const string& errmsg)
	//{
	//	//cout << errmsg << endl;
	//	cout << "delete" << p1 << endl;
	//	delete[] p1;

	//	throw errmsg;  // 重新抛出
	//}

	cout << "delete"<< p1 << endl;
	delete[] p1;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg) 
	{
		cout << errmsg << endl;
		// 记录日志,进行统一处理
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}

	return 0;
}
那么就可以在catch捕获异常里面,释放内存以后,再重新抛出异常throw;(但不知道是什么类型)  ,若无类型匹配就会直接停止程序,于是有 catch(...)可以捕获任意类型的异常,释放空间时,就可以直接在catch中写一次就可以。(但也不知道异常错误是什么)
所以为了在main中统一处理异常,这就要要求抛异常得规范,使得捕获以后可以识别异常类型。(后面继续看)
5. 实际中抛出和捕获的匹配原则有个例外,并不都是类型完全匹配,可以抛出的派生类对象,

使用基类捕获,这个在实际中非常实用,后面会详细讲解。

异常规范:
// C++11 中新增的 noexcept ,表示不会抛异常
thread () noexcept ;
thread ( thread && x ) noexcept ;

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

1. 首先检查throw本身是否在try块内部,如果是再查找匹配的catch语句。如果有匹配的,则
调到catch的地方进行处理。
  
前面我们提到了,try中是保护代码,如果try中throw抛异常以后,首先看有没有对应的catch(类型匹配),没有的话直接报错,停止程序,有的话直接跳到catch来进一步解决。
2. 没有匹配的catch则退出当前函数栈,继续在调用函数的栈中进行查找匹配的catch。
当然不是当前函数栈没有对应的catch就直接终止程序,会依次返回调用链的上一层去寻找,
知道调用链结束,没找到就直接报错,停止程序
 
3. 如果到达main函数的栈,依旧没有匹配的,则终止程序。上述这个沿着调用链查找匹配的
catch子句的过程称为栈展开。所以实际中我们最后都要加一个catch(...)捕获任意类型的异
常,否则当有异常没捕获,程序就会直接终止。
 
当然程序一般不会随便就结束,若没有对应的catch捕获异常匹配,为了防止程序结束,就会在最后加一个catch(...){},防止程序结束。
4. 找到匹配的catch子句并处理以后,会继续沿着catch子句后面继续执行。      

4.异常的特殊类型匹配(异常体系)

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家
随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。
这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。

这就有了用子类抛异常,父类来捕获异常。什么意思呢?来看例子:

//规定一个异常的标准类
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;
	}
};

class HttpServerException : public Exception
{
public:
	HttpServerException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		, _type(type)
	{}

	virtual string what() const
	{
		string str = "HttpServerException:";
		str += _type;
		str += ":";
		str += _errmsg;

		return str;
	}

private:
	const string _type;
};

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

	cout << "调用成功" << endl;
}

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

	SQLMgr();
}

void HttpServer()
{
	// ...
	srand(time(0));
	if (rand() % 3 == 0)
	{
		throw HttpServerException("请求资源不存在", 100, "get");
	}
	else if (rand() % 4 == 0)
	{
		throw HttpServerException("权限不足", 101, "post");
	}

	CacheMgr();
}

int main()
{
	while (1)
	{
		Sleep(1000);

		try 
        {
			HttpServer();
		}
		catch (const Exception& e) // 这里捕获父类对象就可以
		{
			// 多态
			cout << e.what() << endl;
		}

		catch (...)
		{
			cout << "Unkown Exception" << endl;
		}
	}

	return 0;
}

 可以来看一下结果:

 catch(...){}的作用就是方式其他无匹配的异常类型报错停止程序。

这里其实也用到了多态的调用,通过父类的引用,来调用重写以后的虚函数,从而实现多态调用。

这就很好的解决了问题。

 

C++ 提供了一系列标准的异常 ,我们可以在程序中使用这些标准的异常。
但是
实际中我们可以可以去继承 exception 类实现自己的异常类。但是实际中很多公司像上面一
样自己定义一套异常继承体系。因为 C++ 标准库设计的不够好用。
只需知道这些异常代表的意义:
(申请内存空间)

(越界访问)

5.异常安全

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

6.异常的优缺点

优点:

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

2. 返回错误码的传统方式有个很大的问题就是,在函数调用链中,深层的函数返回了错误,那
么我们得层层返回错误,最外层才能拿到错误。但C++的异常可以直接跳转到捕获异常的位置。
错误码返回要层层判断,当前遇到错误,返回上一层要判断返回的错误码。

缺点:

1. 异常会导致程序的执行流乱跳,并且非常的混乱,并且是运行时出错抛异常就会乱跳。这会
导致我们跟踪调试时以及分析程序时,比较困难。
2. 异常会有一些性能的开销。当然在现代硬件速度很快的情况下,这个影响基本忽略不计。
3. C++没有垃圾回收机制,资源需要自己管理。有了异常非常容易导致内存泄漏、死锁等异常
安全问题。这个需要使用RAII来处理资源的管理问题。学习成本较高。

总之,利大于弊!

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

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

相关文章

ChatGPT写小论文

ChatGPT写小论文 只是个人对写小论文心得?从知乎,知网自己总结的,有问题,可以留个言我改一下 文章目录 ChatGPT写小论文-1.写论文模仿实战(狗头)0.论文组成1.好论文前提:2.标题3.摘要4.关键词5.概述6.实验数据、公式或者设计7.结论&#xff0c;思考8.参考文献 0.模仿1.喂大纲…

【云原生】Dockerfile制作WordPress镜像,实现Compose + K8s编排部署

文章目录 &#x1f479; 关于作者前言环境准备目录结构 dockerfile制作镜像yum 脚本Dockerfile-mariadb 镜像Dockerfile-service 镜像docker compose 编排 K8s部署svcdeploy ✊ 最后 &#x1f479; 关于作者 大家好&#xff0c;我是秋意零。 &#x1f608; CSDN作者主页 &…

lambda的toMap是不是要注意点,线上事故

异常回顾 先看代码&#xff1a; dbTaxiDrivers.ifPresent((drivers) -> { map.putAll(drivers.stream() .collect(Collectors.toMap(TaxiDriverInfo::getOperationId, item -> item))); }); 相信很多为了减少2层for循环&#xff0c…

✨✨✨ ❃ ♕ ꕥ Xpath解析html获取表情符号,丰富你的文章 ꧁ ꧂꧁ ꧂

✨✨✨ ❃ ♕ ꕥ Xpath解析html获取表情符号&#xff0c;丰富你的文章 ꧁ ꧂꧁ ꧂ 1. 推荐几个好玩的表情符号网站2. xpath解析html获取表情3. xpath解析html源码3.1 parse_li.py3.2 symbol2.html 参考 1. 推荐几个好玩的表情符号网站 &#x1f495; &#x1f9da; &#x1f6…

让科幻照进现实,未来出行革命

在“新四化”&#xff08;电动化、网联化、智能化、共享化&#xff09;和“碳中和、碳达峰”双碳目标下&#xff0c;中国汽车产业正经历着前所未有之大变局。作为“智能化”核心之一的自动驾驶&#xff0c;在监管、技术和商业化方面持续积累、不断完善&#xff0c;即将迈入发展…

案例1:Java超市管理系统设计与实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

浏览器【控制台】的小妙招-dom复制

了解前端开发的朋友应该对浏览器的控制台非常熟悉&#xff0c;毕竟日常里除了wife就是跟浏览器相处的最久了。 1、唤出控制台 打开一个网页&#xff1a; 按下键盘【F12】或者鼠标在网页任意位置【右键- 检查】&#xff0c;即可唤出浏览器的【控制台】 2、日常开发使用之【复制…

QuickTime Player + BlackHole解决Mac不能录内部声音

背景 在用mac录制屏幕时&#xff0c;发现不能录入电脑内的声音。 App Store中有一些收费的屏幕录制软件&#xff08;也需要安装特定的虚拟声卡&#xff09;。 现在实现完全免费的屏幕录制&#xff0c;QuickTime Player BlackHole组合&#xff0c;QuickTime Player是mac自带…

Android 12系统源码_CarService(一)CarService的基本架构

前言 1、Google官网上是这样介绍汽车架构的。 Car App&#xff1a;包括OEM和第三方开发的AppCar API&#xff1a;内有包含 CarSensorManager 在内的 API。位于 /platform/packages/services/Car/car-lib。CarService&#xff1a;系统中与车相关的服务&#xff0c;位于 /platfo…

报错记录:构造方法获取不了@Value的值,问题刨析与解决方案

报错记录&#xff1a;构造方法获取不了Value的值&#xff0c;问题刨析与解决方案 有时我们需要在构造函数中初始化属性&#xff0c;之前老的项目是用的I/O流来获取配置文件的值&#xff0c;目前配置转为线上&#xff0c;使用Apollo来获取值&#xff0c;由于获取Apollo的值被封装…

浅述 国产仪器 1761程控模块电源

1761程控模块电源是在自动测试环境中提供偏置功率和对部件或最终产品提供激励的理想设备&#xff0c;是测试系统必备的测试仪器。适用于研发、设计、生产制造等自动测试领域。 1761程控模块电源为用户选配电源提供了灵活性&#xff0c;根据需要可选购1&#xff5e;8种&#xff…

6.登录token

登录时生产token和refreshtoken&#xff0c;请求时带上token&#xff0c;后台校验&#xff0c;通过的话则进行处理&#xff0c;否则返回错误信息&#xff08;token失效过期等等&#xff09;&#xff0c;校验不通过会调用刷新token的接口&#xff0c;重新获取token&#xff0c;如…

< 封装公共导出模块:配合element实现提示 >

封装公共导出模块 &#x1f449; 前言&#x1f449; 一、原理&#x1f449; 二、实现案例&#x1f449; 三、效果演示往期内容 &#x1f4a8; &#x1f449; 前言 在 Vue elementUi 开发中&#xff0c;我们偶尔会遇到需要导出的列表&#xff0c;或者指定位置的导出内容。在一…

神经网络基础-手写数字识别

手写数字识别神经网络 基本原理 图像本质上被认为是一个矩阵&#xff0c;每个像素点都是一个对应的像素值&#xff0c;相当于在多维数据上进行相关的归类或者其他操作。 线性函数 线性函数的一个从输入到输出的映射&#xff0c;用于给目标一个每个类别对应的得分。 图像 ( …

leetcode刷题(9)二叉树(3)

各位朋友们&#xff0c;提前祝大家五一劳动节快乐啊&#xff01;&#xff01;&#xff01;今天我为大家分享的是关于leetcode刷题二叉树相关的第三篇我文章&#xff0c;让我们一起来看看吧。 文章目录 1.二叉树的层序遍历题目要求做题思路代码实现 2.从前序与中序遍历序列构造二…

Authing 正式发布应用集成网关 - Authing Gateway

2023 年 2月&#xff0c; Authing 推出了身份领域的 PaaS化应用集成网关 - Authing Gateway 。 Authing Gateway 提供将原有应用快速集成到 Authing 身份云产品的能力&#xff0c;在扩充身份认证方式的同时&#xff0c;提高资源的安全性和数据的隐私可靠性。 01.Authing Gatew…

如何查看声卡、pcm设备以及tinyplay、tinymix、tinycap的使用

命令列表 功能命令查看当前录音进程状态dumpsys media.audio_flinger查看当前音频策略状态dumpsys media.audio_policy查看pcm节点信息cat /proc/asound/pcm查看声卡信息cat /proc/asound/cards查看声卡物理设备节点ls /dev/snd/驱动层录音命令tinycap xx.wav -D 0 -d 1 -c 2 …

【Java EE】-博客系统(前端页面)

作者&#xff1a;学Java的冬瓜 博客主页&#xff1a;☀冬瓜的主页&#x1f319; 专栏&#xff1a;【JavaEE】 分享: 且视他人如盏盏鬼火&#xff0c;大胆地去走你的道路。——史铁生《病隙碎笔》 主要内容&#xff1a;博客系统 登陆页面&#xff0c;列表页面&#xff0c;详情页…

OpenAI推企业版ChatGPT,英伟达造AI安全卫士

GPT现在已经进入了淘金时代。虽然全球涌现出成千上万的大模型或ChatGPT变种&#xff0c;但一直能挣钱的人往往是卖铲子的人。 这不&#xff0c;围绕暴风眼中的大模型&#xff0c;已经有不少企业&#xff0c;开始研究起了大模型的“铲子”产品&#xff0c;而且开源和付费两不误…

【C++】——string的功能介绍及使用

前言&#xff1a; 在上期&#xff0c;我们简单的介绍了关于 模板和STL &#xff0c;今天我就带领大家学习一下关于 【string】类。本期&#xff0c;我们主要讲解的是关于 【string】的基本介绍以及【string】类的常用接口说明。有了以上的基本认识之后&#xff0c;在下期&…