IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 异常处理 -> 正文阅读

[C++知识库]C++ 异常处理

传统的错误处理方式

如果程序发生了严重的错误,在C语言中,一般的处理方式是:返回错误码,严重时直接终止程序。对于错误码,很多系统调用和库调用的接口都是把错误码放到errno变量中,使用者需要通过错误码查找对应错误信息,而直接终止程序的做法未免有些暴力,对于程序发生错误的处理方式,C++使用抛异常来解决。

何为异常?

异常作为一种错误的处理方式,在程序发生无法处理的错误时,程序会抛出异常,让使用者解决这个错误。

throw:程序出现错误时,用throw抛出错误
catch:catch用来接收throw抛出的错误
try:try内抛出的异常才会被catch捕获,try后面必须跟一个或多个catch块


double Division(int num1, int num2)
{
	if (num2 == 0)
		throw "Division by zero condition!";
	else
		return num1 * 1.0 / num2;
}

int main()
{
	try
	{
		Division(1, 0);
	}
	catch (const char* str)
	{
		cout << str << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	cout << "after catch" << endl;
	return 0;
}

比如上面的代码,try块中调用了Division函数,当被除数为0时,Division函数会抛出异常对象"Division by zero condition!",很显然这是一个const char*对象,catch的参数列表接收并打印这个对象。 至于最后的catch,参数列表中的三个点则表示接收任意对象,当异常对象没有被catch捕获时,程序才会走到参数为三个点的catch块中。

在异常被捕获后,程序会执行catch块的代码,接着跳过其他catch块,然后运行剩下的代码,所以程序会打印after catch这句话。在这里插入图片描述

异常的使用

异常的语法点

异常由抛出对象引发,抛出对象的类型决定了要调用哪个catch块
被选中的处理代码(catch块)是与抛出对象距离最近并且类型对应的那个
抛出对象会被临时拷贝,产生临时对象,再传给catch块,类似于函数的传值返回
catch(…)可以接收任意类型的异常对象,但主要的问题是不知道发生的错误是什么
可以用父类对象接收类型为子类对象的异常,这个过程发生切片,但在异常的捕获中经常使用

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

一个异常被抛出,先检查异常是否在try块中,如果不在则退出当前函数,到调用它的函数块中,检查异常是否在该函数栈中的try块中被抛出,如果在则寻找对应的catch块执行,如果找不到对应的catch块,再推出当前函数栈,重复上面的步骤,直到main函数栈中找不到catch块,程序直接终止。如果执行完catch块,程序会执行后面的代码。

以上面的代码为例,Division抛出了异常,但Division中没有try块,程序退出当前函数栈,到了main函数栈,发现异常在try块中,接着寻找与抛出对象类型相同的catch块,找到了运行其代码:打印出错误信息。

double Division(int num1, int num2)
{
	try
	{
		if (num2 == 0)
			throw "Division by zero condition!";
		else
			return num1 * 1.0 / num2;
	}
	catch (const char* str)
	{
		cout << str << endl;
	}
}

int main()
{
	try
	{
		Division(1, 0);
	}
	catch (const char* str)
	{
		cout << str << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	cout << "after catch" << endl;
	return 0;
}

如果改写之前的代码,Division中抛出异常,throw在当前函数栈的try块中,则在当前函数栈中寻找catch块,成功找到后,打印其错误信息。

try块的嵌套使用

当异常抛出,需要对异常进行特殊处理的时候,可以嵌套使用try块,比如发送微信信息失败,可能是网络错误,也可能是没有对方好友的权限,当捕获网络错误的异常时,需要重新发送信息几次,而捕获权限不足的异常时,不需要重新发送消息,只要显示“不是对方好友”的提示即可。

下面用代码模拟这个过程。

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

		virtual string what()
		{
			return _errmsg;
		}
		int errid()
		{
			return _id;
		}

	protected:
		int _id;
		string _errmsg;
	};

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

		string what()
		{
			string str = "HttpServeException:";
			str += _errmsg;
			return str;
		}
	};

	void SeedMsg(const string& str)
	{
		if (rand() < RAND_MAX - 10000)
		{
			throw HttpServeException("网络错误", 1);
		}
		else if (rand() < RAND_MAX / 2)
		{
			throw HttpServeException("权限不足,你已不是对方好友", 2);
		}
		else
		{
			cout << "信息发送成功" << endl;
		}
	}
}

int main()
{
	while (1)
	{
		try
		{
			for (int i = 0; i < 10; i++)
			{
				try
				{
					Sleep(500);
					test::SeedMsg("hello");
					break;
				}
				catch (test::HttpServeException& exception)
				{
					if (exception.errid() == 1)
					{
						cout << "网络错误,重新发送消息次数为" << i << endl;
					}
					else
					{
						throw exception;
					}
				}
			}
		}
		catch (test::Exception& exception)
		{
			cout << exception.what() << endl;
		}
	}
	return 0;
}

先说明代码逻辑,我定义了一个基类Exception,用来表示异常的基本信息,再定义了一个派生类HttpServeException,其重写了基类Exception的what函数,打印返回的异常信息时,可以得知异常的类型。

然后模拟发送消息的过程,用for循环十次SeedMsg函数,该函数通过产生随机数,通过随机数的范围判断是否需要抛出异常(模拟程序遇到错误的情况),for循环在外面的try块中,for循环调用SeedMsg函数在内try块中,如果SeedMsg函数没有抛出异常,break退出for循环。如果发生了异常,在内try块下捕获异常,判断异常的错误码(是否是网络错误),如果是网络错误,打印信息提示(实际这串信息没必要打印,这里是方便观察现象),如果不是网络错误则再次抛出异常(即退出了for循环,不重复发送消息),程序走到外try块的catch中,由其捕获再次抛出的异常。

最后将程序放到死循环中,一次次的模拟发送消息的过程
在这里插入图片描述
可以看到,出现网络错误,程序会重复发送消息,但重复次数有限。使用try的嵌套后,该程序做到了重复发送消息,并对发送过程中产生的特殊异常进行处理,其他异常重新抛出的功能。

异常涉及的资源管理问题(异常安全)

如果一段程序申请了资源,但在资源释放之前抛出异常,则资源不会释放,因为程序跳转到catch块中执行,略过了资源释放语句,结果是造成了内存泄漏。

void Func()
{
	int* p = nullptr;
	p = new int[1024 * 1024];

	if (rand() < RAND_MAX / 2)
	{
		throw test::Exception("异常发生", 1);
	}

	delete[] p;
	cout << __LINE__ << "delete[] p" << endl;
}

int main()
{
	while (1)
	{
		try
		{
			Func();
		}
		
		catch (test::Exception& e)
		{
			cout << e.what() << endl;
		}
	}
	return 0;
}

Func函数申请完内存后,发生了异常,程序跳转到main函数中的catch块,略过了释放资源的语句,造成了内存泄漏。
在这里插入图片描述
所以申请资源后,如果异常可能发生,就需要在当前函数栈中特地捕获这个异常进行资源释放

void Func()
{
	int* p = nullptr;
	p = new int[1024 * 1024];
	// 捕获异常并完成资源释放
	try
	{
		if (rand() < RAND_MAX / 2)
		{
			throw test::Exception("异常发生", 1);
		}
	}
	catch (test::Exception& e)
	{
		delete[] p;
		throw;
	}

	delete[] p;
	cout << __LINE__ << "delete[] p" << endl;
}

异常规范

C++98的异常规范是:需要在每个函数后声明该函数是否会抛出异常,并且说明抛出什么异常。

void Func1() throw() // 不会抛异常
void Func2() throw(A, B) // 会抛出A,B类型
void Func3() throw(std::bad_alloc) // 抛出bad_alloc异常

每个异常的类型都要写出,实在有些麻烦,C++11简化了这个规则,当函数不抛出异常时,只要在函数后声明noexcept

void Func4() noexcept

异常的优缺点

异常的优点:

1.比起错误码,异常能返回直观详细的错误消息,可以更好地定位bug
2.比起错误码,调用函数出现错误时,错误码只能通过return语句层层返回,而异常能够直接跳转到catch中返回错误
3.对于一些函数,如构造,析构,使用异常能更好的处理错误,因为它们没有返回值,不能返回错误码,只能在函数中抛出异常

异常的缺点:

1.异常的跳转类似C语言中的goto语句,在大型的工程中跳转使得程序执行流非常混乱,在调用分析程序时,异常的存在使其变得困难
2.异常没有垃圾回收机制,要处理资源只能使用智能指针,增加了学习成本
3.标准库中的异常定义的不太好,导致大家定义自己的异常体系,没有一个统一的标准使得异常非常混乱

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:13:43  更:2022-10-17 12:18:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 2:49:10-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码