异常--C++

news2025/2/5 17:52:33

文章目录

  • 一、异常的概念及使用
    • 1、异常的概念
    • 2、异常的抛出和捕获
    • 3、栈展开
    • 4、查找匹配的处理代码
    • 5、异常重新抛出
    • 6、异常安全问题
    • 7、异常规范
  • 二、标准库的异常

一、异常的概念及使用

1、异常的概念

  1. 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理,异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另一部分,检测环节无须知道问题的处理模块的所有细节。
  2. C语言主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。异常时抛出一个对象,这个对象可以含更全面的各种信息。

2、异常的抛出和捕获

  1. 程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前的调用链决定了应该由哪个catch的处理代码来处理该异常。
  2. 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。根据抛出对象的类型和内容,程序的抛出异常部分告知异常处理部分到底发生了什么错误。
  3. 当throw执行时,throw后面的语句将不再被执行。程序的执行从throw位置跳到与之匹配的catch模块,catch可能是同一函数中的一个局部的catch,也可能是调用链中另一个函数中的catch,控制权从throw位置转移到了catch位置。这里还有两个重要的含义:
    1、沿着调用链的函数可能提早退出。
    2、一旦程序开始执行异常处理程序,沿着调用链创建的对象都将销毁。
  4. 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个局部对象,所以会生成一个拷贝对象,这个拷贝的对象会在catch子句后销毁。(这里的处理类似于函数的传值返回)

3、栈展开

  1. 抛出异常后,程序暂停当前函数的执行,开始寻找与之匹配的catch子句,⾸先检查throw本身是否在try块内部,如果在则查找匹配的catch语句,如果有匹配的,则跳到catch的地方进行处理。
  2. 如果当前函数中没有try/catch子句,或者有try/catch子句但是类型不匹配,则退出当前函数,继续在外层调用函数链中查找,上述查找的catch过程被称为栈展开。
  3. 如果到达main函数,依旧没有找到匹配的catch子句,程序会调用标准库的 terminate 函数终止程序。
  4. 如果找到匹配的catch子句处理后,catch子句代码会继续执行。
    在这里插入图片描述
double Divide(int a, int b)
{
	try
	{
		// 当b == 0时抛出异常
		if (b == 0)
		{
			string s("Divide by zero conditon!");
			throw s;
		}
		else
		{
			return ((double)a / (double)b);
		}
	}
	catch (int errid)
	{
		cout << errid << endl;
	}
	return 0;
}

void Func()
{
	int len, time;
	cin >> len >> time;
	try
	{
		cout << Divide(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	cout << __FUNCTION__ << ":" << __LINE__ << "行执行" << endl;
}
int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		catch (const string& errmsg)
		{
			cout << errmsg << endl;
		}
	}
	return 0;
}

4、查找匹配的处理代码

  1. 一般情况下抛出对象和catch是类型完全匹配的,如果有多个类型匹配的,就选择离他位置更近的那个。
  2. 但是也有一些例外,允许从非常量向常量的类型转换,也就是权限缩小;允许数组转换成指向数组元素类型的指针,函数被转换成指向函数的指针;允许从派生类向基类类型的转换,这个点非常实用,实际中继承体系基本都是用这个方式设计的。
  3. 如果到main函数,异常仍旧没有被匹配就会终止程序,不是发生严重错误的情况下,我们是不期望程序终止的,所以一般main函数中最后都会使用catch(…),它可以捕获任意类型的异常,但是是不知道异常错误是什么。
#include<thread>
// ⼀般⼤型项⽬程序才会使⽤异常,下⾯我们模拟设计⼀个服务的⼏个模块
// 每个模块的继承都是Exception的派⽣类,每个模块可以添加⾃⼰的数据
// 最后捕获时,我们捕获基类就可以
class Exception
{
public:
	Exception(const string& errmsg, int id)
		:_errmsg(errmsg)
		,_id(id)
	{}

	virtual string what() const
	{
		return _errmsg;
	}

	int getid() const
	{
		return _id;
	}

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 HttpException : public Exception
{
public:
	HttpException(const string& errmsg, int id, const string& type)
		:Exception(errmsg, id)
		,_type(type)
	{}
	virtual string what() const
	{
		string str = "HttpException:";
		str += _type;
		str += ":";
		str += _errmsg;
		return str;
	}
private:
	const string _type;
};

void SQLMgr()
{
	if (rand() % 7 == 0)
	{
		throw SqlException("权限不足", 100, "select * from name = '张三'");
	} 
	else
	{
		cout << "SQLMgr 调用成功" << endl;
	}
}

void CacheMgr()
{
	if (rand() % 5 == 0)
	{
		throw CacheException("权限不足", 100);
	} 
	else if (rand() % 6 == 0)
	{
		throw CacheException("数据不存在", 101);
	} 
	else
	{
		cout << "CacheMgr 调用成功" << endl;
	} 
	SQLMgr();
} 

void HttpServer()
{
	if (rand() % 3 == 0)
	{
		throw HttpException("请求资源不存在", 100, "get");
	} 
	else if (rand() % 4 == 0)
	{
		throw HttpException("权限不足", 101, "post");
	} 
	else
	{
		cout << "HttpServer调用成功" << endl;
	} 
	CacheMgr();
} 

int main()
{
	srand(time(0));
	while (1)
	{
		this_thread::sleep_for(chrono::seconds(1));
		try
		{
			HttpServer();
		} 
		catch(const Exception & e) // 这⾥捕获基类,基类对象和派⽣类对象都可以被捕获
		{
			cout << e.what() << endl;
		} 
		catch(...)
		{
			cout << "Unkown Exception" << endl;
		}
	} 

	return 0;
}

5、异常重新抛出

有时catch到一个异常对象后,需要对错误进行分类,其中的某种异常错误需要进行特殊的处理,其他错误则重新抛出异常给外层调用链处理。捕获异常后需要重新抛出,直接 throw; 就可以把捕获的对象直接抛出。

// 下⾯程序模拟展⽰了聊天时发送消息,发送失败补货异常,但是可能在
// 电梯地下室等场景⼿机信号不好,则需要多次尝试,如果多次尝试都发
// 送不出去,则就需要捕获异常再重新抛出,其次如果不是⽹络差导致的
// 错误,捕获后也要重新抛出。
void _SeedMsg(const string& s)
{
	if (rand() % 2 == 0)
	{
		throw HttpException("⽹络不稳定,发送失败", 102, "put");
	}
	else if (rand() % 7 == 0)
	{
		throw HttpException("你已经不是对象的好友,发送失败", 103, "put");
	} 
	else
	{
	cout << "发送成功" << endl;
	}
}
void SendMsg(const string& s)
{
	// 发送消息失败,则再重试3次
	for (size_t i = 0; i < 4; i++)
	{
		try
		{
			_SeedMsg(s);
			break;
		} 
		catch(const Exception & e)
		{
			// 捕获异常,if中是102号错误,⽹络不稳定,则重新发送
			// 捕获异常,else中不是102号错误,则将异常重新抛出
			if (e.getid() == 102)
			{
				// 重试三次以后否失败了,则说明⽹络太差了,重新抛出异常
				if (i == 3)
					throw;

				cout << "开始第" << i + 1 << "重试" << endl;
			} 
			else
			{
				throw;
			}
		}
	}
}
int main()
{
	srand(time(0));
	string str;
	while (cin >> str)
	{
		try
		{
			SendMsg(str);
		} 
		catch(const Exception & e)
		{
			cout << e.what() << endl << endl;
		} 
		catch(...)
		{
			cout << "Unkown Exception" << endl;
		}
	} 
	return 0;
}

6、异常安全问题

  1. 异常抛出后,后面的代码就不再执行,前面申请了资源(内存、锁等),后面进行释放,但是中间可能会抛异常就会导致资源没有释放,这里由于异常就引发了资源泄漏,产生安全性的问题。中间我们需要捕获异常,释放资源后面再重新抛出,当然后面智能指针章节讲的RAII方式解决这种问题是更好的。
  2. 其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。《Effctive C++》第8个条款也专门讲了这个问题,别让异常逃离析构函数。
double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero conditon!";
	}
	return ((double)a / (double)b);
}

void Func()
{
	// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array没有得到释放。
	// 所以这里捕获异常后并不处理异常,异常还是交给外层处理,这里捕获了再
	// 重新抛出去。
	int* array = new int[10];
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...)
	{
		// 捕获异常释放内存
		cout << "delete []" << array << endl;
		delete[] array;
		throw;
	}
	cout << "delete []" << array << endl;
	delete[] array;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}
	return 0;
}

7、异常规范

  1. 对于用户和编译器而言,预先知道某个程序会不会抛出异常大有裨益,知道某个函数是否会抛出异常有助于简化调用函数的代码。
  2. C++98中函数参数列表的后面接throw(),表示函数不抛异常,函数参数列表的后面接throw(类型1,类型2…)表示可能会抛出多种类型的异常,可能会抛出的类型用逗号分割。
  3. C++98的方式这种方式过于复杂,实践中并不好用,C++11中进行了简化,函数参数列表后面加noexcept表示不会抛出异常,啥都不加表示可能会抛出异常。
  4. 编译器并不会在编译时检查noexcept,也就是说如果一个函数用noexcept修饰了,但是同时又包含了throw语句或者调用的函数可能会抛出异常,编译器还是会顺利编译通过的(有些编译器可能会报个警告)。但是一个声明了noexcept的函数抛出了异常,程序会调用 terminate 终止程序。
  5. noexcept(expression)还可以作为一个运算符去检测一个表达式是否会抛出异常,可能会则返回false,不会就返回true。
// C++98
// 这⾥表⽰这个函数只会抛出bad_alloc的异常
void* operator new (std::size_t size) throw (std::bad_alloc);
// 这⾥表⽰这个函数不会抛出异常
void* operator delete (std::size_t size, void* ptr) throw();

// C++11
size_type size() const noexcept;
iterator begin() noexcept;
const_iterator begin() const noexcept;

double Divide(int a, int b) //noexcept
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero conditon!";
	}
	return ((double)a / (double)b);
}

int main()
{
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "Unkown Exception" << endl;
	}

	int i = 0;
	cout << noexcept(Divide(1, 2)) << endl;
	cout << noexcept(Divide(1, 0)) << endl;
	cout << noexcept(++i) << endl;

	return 0;
}

二、标准库的异常

  1. https://legacy.cplusplus.com/reference/exception/exception/
  2. C++标准库也定义了一套自己的一套异常继承体系库,基类是exception,所以我们日常写程序,需要在主函数捕获exception即可,要获取异常信息,调用what函数,what是一个虚函数,派生类可以重写。
    在这里插入图片描述

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

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

相关文章

字符串p型编码

字符串p型编码 C 语言实现C 实现Java 实现Python 实现 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 给定一个完全由数字字符&#xff08;‘0’,‘1’,‘2’,…,‘9’&#xff09;构成的字符串str&#xff0c;请写出str的p型编码串。例如&…

UIlicious - 自动化端到端测试

在现代软件开发中&#xff0c;测试自动化已然成为产品交付质量的基石。而端到端测试&#xff08;E2E&#xff09;&#xff0c;作为验证整个应用流畅运行的关键&#xff0c;常常是测试工作中最具挑战性的一环。这时&#xff0c;一款简单高效的自动化测试工具——UIlicious&#…

机器学习:机器学习项目的完整周期

建立一个有价值的机器学习系统时&#xff0c;需要考虑和计划哪些步骤&#xff1f; 以语音识别为例演示机器学习项目的全周期&#xff1a;机器学习项目的第一步是对项目进行范围划分&#xff0c;即决定什么是项目和你想做什么&#xff0c;然后是收集数据&#xff0c;所以决定需…

浪潮X86服务器NF5280、8480、5468、5270使用inter VROC Raid key给NVME磁盘做阵列

Inter VROC技术简介 Intel Virtual RAID on CPU (Intel VROC) 简单来说就是用CPU的PCIE通道给NVME硬盘做Raid 更多信息可以访问官方支持页面 Raid Key 授权&#xff0c;即VROC SKU 授权主要有用的有2个标准和高级&#xff0c;仅Raid1的授权我暂时没见过。 标准 VROCSTANMOD …

ROS基本框架2——在ROS开发中创建并使用自定义消息(C++版本)

ROS基本框架2——在ROS开发中创建并使用自定义消息(C++版本) code review! 参考笔记 1.ROS基本框架1——编写简单的发布者和订阅者(C++和Python版本) 2.ROS基本框架2——在ROS开发中创建并使用自定义消息(C++版本) 文章目录 ROS基本框架2——在ROS开发中创建并使用自定义…

鸿蒙征文|鸿蒙技术分享:使用到的开发框架和技术概览

目录 每日一句正能量前言正文1. 开发环境搭建关键技术&#xff1a;2. 用户界面开发关键技术&#xff1a;3. 应用逻辑开发关键技术&#xff1a;4. 应用测试关键技术&#xff1a;5. 应用签名和打包关键技术&#xff1a;6. 上架流程关键技术&#xff1a;7. 后续维护和更新关键技术…

(长期更新)《零基础入门 ArcGIS(ArcMap) 》实验二----网络分析(超超超详细!!!)

相信实验一大家已经完成了&#xff0c;对Arcgis已进一步熟悉了&#xff0c;现在开启第二个实验 ArcMap实验--网络分析 目录 ArcMap实验--网络分析 1.1 网络分析介绍 1.2 实验内容及目的 1.2.1 实验内容 1.2.2 实验目的 2.2 实验方案 2.3 实验流程 2.3.1 实验准备 2.3.2 空间校正…

go语言 Pool实现资源池管理数据库连接资源或其他常用需要共享的资源

go Pool Pool用于展示如何使用有缓冲的通道实现资源池&#xff0c;来管理可以在任意数量的goroutine之间共享及独立使用的资源。这种模式在需要共享一组静态资源的情况&#xff08;如共享数据库连接或者内存缓冲区&#xff09;下非 常有用。如果goroutine需要从池里得到这些资…

马铃薯病害识别(VGG-16复现)

VGG16-Pytorch实现马铃薯病害识别 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 电脑系统&#xff1a;Windows11 显卡型号&#xff1a;NVIDIA Quadro P620 语言环境&#xff1a;python 3.9.7 编译器&am…

HCSIF: 中国区域2000-2022年高时空分辨率(500m)SIF数据集

日光诱导叶绿素荧光&#xff08;Solar-induced chlorophyll fluorescence, SIF&#xff09;被誉为“植被光合作用的探针”。2017年&#xff0c;搭载在Sentinel-5P卫星上的 TROPOMI (TROPOspheric Monitoring Instrument&#xff09;传感器成功发射&#xff0c;该卫星同时具有高…

STL:相同Size大小的vector和list哪个占用空间多?

在C中&#xff0c;vector和list是两种不同的序列容器。vector底层是连续的内存&#xff0c;而list是非连续的&#xff0c;分散存储的。因此&#xff0c;vector占用的空间更多&#xff0c;因为它需要为存储的元素分配连续的内存空间。 具体占用多少空间&#xff0c;取决于它们分…

蓝牙设备驱动开发

文章目录 一、蓝牙协议架构二、蓝牙协议的HCI传输层三、编程框架 一、蓝牙协议架构 蓝牙是无线数据和语音传输的开放式标准&#xff0c;它将各种通信设备、计算机及其终端设备、各种数字数据系统、甚至家用电器采用无线方式联接起来。它的传输距离为10cm&#xff5e;10m&#…

【计算机网络】实验7:默认路由和特定主机路由以及路由环路问题

实验 7&#xff1a;默认路由和特定主机路由以及路由环路问题 一、 实验目的 了解默认路由以及特定主机路由。 了解静态路由配置错误导致的路由环路问题。 二、 实验环境 • Cisco Packet Tracer 模拟器 三、 实验内容 1、默认路由以及特定主机路由 (1) 第一步&#xff…

安装 RabbitMQ 服务

安装 RabbitMQ 服务 一. RabbitMQ 需要依赖 Erlang/OTP 环境 (1) 先去 RabbitMQ 官网&#xff0c;查看 RabbitMQ 需要的 Erlang 支持&#xff1a;https://www.rabbitmq.com/ 进入官网&#xff0c;在 Docs -> Install and Upgrade -> Erlang Version Requirements (2) …

MiniProfiler WebAPI 分析工具

一、介绍&#x1f6e0;️ MiniProfiler 是一款简单但有效的 .NET、Ruby、Go 和 Node.js 微型 性能分析器 。 MiniProfiler 不会将自身附加到每个方法调用;那会太具有侵入性&#xff0c;并且不会专注于最大的性能问题。相反&#xff0c;它提供&#xff1a; &#x1f538;ADO.…

Java个人博客系统项目文档

项目名称 Java个人博客系统 项目概述 该博客系统是一个多功能的Java应用程序。该系统支持用户发布新文章、浏览他人文章、管理个人文章收藏和删除不再需要的文章。通过该博客系统&#xff0c;用户可以享受一个安全、便捷的在线写作和阅读体验。 运行环境 编程语言&#xff1…

华为HarmonyOS 让应用快速拥有账号能力 - 获取用户头像昵称

场景介绍 如应用需要完善用户头像昵称信息&#xff0c;可使用Account Kit提供的头像昵称授权能力&#xff0c;用户允许应用获取头像昵称后&#xff0c;可快速完成个人信息填写。以下只针对Account kit提供的头像昵称授权能力进行介绍&#xff0c;若要获取头像还可通过场景化控…

Hadoop生态圈框架部署 伪集群版(一)- Linux操作系统安装及配置

文章目录 前言一、下载CentOS镜像1. 下载 二、创建虚拟机hadoop三、CentOS安装与配置1. 安装CentOS2. 配置虚拟网络及虚拟网卡2.1 配置虚拟网络2.2 配置虚拟网卡 3. 安装 SSH 远程连接工具 FinalShell3.1 简介3.2 下载和安装3.2.1 下载3.2.2 安装 3.3 查看动态ip地址3.4 使用Fi…

StarRocks存算分离在得物的降本增效实践

一、背景 OLAP引擎在得物的客服、风控、供应链、投放、运营、ab实验等大量业务场景发挥重要作用&#xff0c;在报表、日志、实时数仓等应用场景都有广泛的应用。 得物引入和使用OLAP引擎的过程中&#xff0c;每个业务都基于自己的需求选择当时最适合自己的引擎。现在得物内部同…

L15.【LeetCode笔记】相同的树

目录 1.题目 代码模板 2.分析 通过合理的if判断分类讨论两个根节点 1.首先,p和q都为NULL的情况最好排除 2.排除了两个都为NULL的情况,剩下的情况:1.其中一个为NULL;2.两个都不为NULL 写法1 写法2 3.只剩下最后一种情况:p和q都不为NULL 3.代码 提交结果 1.题目 https…