C++进阶(11)异常

个人主页:仍有未知等待探索-CSDN博客

专题分栏:C++

目录

一、什么是异常?

二、异常的抛出和捕获

1、异常抛出和匹配原则

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

三、异常的重新抛出和捕获

错误样例:

正确样例:

四、异常的安全


一、什么是异常?

异常:是指程序在运行的过程中发生的一些异常事件,如,除零溢出、数组下标越界、所要读取的文件不存在、空指针、内存不足、访问非法内存等。

在实际开发中,异常是一个类

二、异常的抛出和捕获

异常抛出和捕获的关键字:throw try-catch

对于下列的样例,就诠释了异常抛出和捕获的大体用法。

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

int main()
{
	try
	{
		//throw 1;
		//throw 1.1;
		//throw 'z';
		throw "af2455";
	}
	catch (int e)
	{
		//解决方案或者提示
		cout << "int 异常" << endl;
	}
	catch (double e)
	{
		//解决方案或者提示
		cout << "double 异常" << endl;
	}
	catch (char e)
	{
		//解决方案或者提示
		cout << "char 异常" << endl;
	}
	catch (...)
	{
		//解决方案或者提示
		cout << "其他异常" << endl;
	}

	return 0;
}

1、异常抛出和匹配原则

  • 异常是通过抛出对象而引发的,该对象的类型,决定应该匹配哪个catch块。
  • 被选中的catch块是与该对象匹配且离抛出异常对象距离最近的那一个。
  • 抛出异常对象后,由于这个异常对象可能是临时对象,所以在抛出的时候会发生一次拷贝,在catch块结束之后,该对象进行析构。
  • catch(...)可以匹配任意类型的异常。
  • 实际上异常的抛出都是:抛派生类,捕获基类

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

  • 首先检查throw本身是否在try块内部,如果在就查找匹配的catch块,如果有匹配的就执行该catch块内部的代码。
  • 如果没有匹配的catch块则退出当前函数栈,继续在调用该函数的函数栈中进行查找catch块。
  • 如果到达main函数的栈,依旧没有匹配的,则终止程序。
  • 找到匹配的catch块之后,执行内部的代码,然后会继续沿着catch块后面继续执行。

一看这个标题,大家可能会有点蒙圈,这是什么意思?我就给大家举个例子来说明一下。

就是向下面的代码一样,try块里面不一定直接抛出异常,也可能是在多个函数嵌套调用之后,在某一个函数内部进行抛出异常。但是该函数内部并没有catch语句,所以要返回调用该函数的函数体内进行查找catch块,以此类推,直到在main函数内部找到了匹配的catch块之后,在执行catch块内部的代码。

#include <iostream>
using namespace std;

void func3()
{
	cout << "func3()" << endl;
	throw 1;
}
void func2()
{
	cout << "func2()" << endl;
	func3();
}
void func1()
{
	cout << "func1()" << endl;
	func2();
}
int main()
{
	try
	{
		func1();
	}
	catch (...)
	{
		cout << "异常" << endl;
	}

	return 0;
}

三、异常的重新抛出和捕获

异常的重新捕获和抛出 --- 让代码顺利完成,不至于跳代码,以至于没有释放空间等。

错误样例:

就比如下面的代码,手动开辟一块空间,调用func1,然后调用func2函数,在func2之后对这块空间进行了释放。但是在func2函数的体内,我们抛出了异常,然后根据上面如果找到匹配的且距离最近的catch块,我们知道,当找到匹配的catch块之后,我们就直接往下面执行了,导致该释放的空间没有释放,发生了内存泄漏。

#include <iostream>
#include <cstdio>
using namespace std;
class A
{
public:
	A() = default;
	int _a;
};
A* a = new A();
void func2()
{
	throw 1;
}
void func1()
{
	func2();
	delete a;
	a = nullptr;
}
int main()
{
	try
	{
		func1();
	}
	catch (...)
	{
		cout << "异常" << endl;
	}

	printf("%p\n", a);
	return 0;
}

正确样例:

在调用delete函数之前,我们应该重新用try函数进行接收一下,然后再catch块中,进行释放。最后我们再重新抛出这个异常,这样就不会有内存泄露了。

#include <iostream>
#include <cstdio>
using namespace std;
class A
{
public:
	A() = default;
	int _a;
};
A* a = new A();

void func2()
{
	throw 1;
}
void func1()
{
	try
	{
		func2();
	}
	catch (int e)
	{
		delete a;
		a = nullptr;

		throw e;
	}
}
int main()
{
	try
	{
		func1();
	}
	catch (...)
	{
		cout << "异常" << endl;
	}

	printf("%p\n", a);
	return 0;
}

四、异常的安全

  • 构造函数完成对象的构造和初始化,最好不要在构造函数中抛出异常,可能会导致对象不完整或者没有完全初始化。
  • 析构函数完成资源的清理,最好不要在析构函数中抛出异常,可能会导致资源泄露。
  • 对于new 和 delete中抛出异常,导致资源的泄漏。我们通常用智能指针解决。
  • 对于lock 和 unlock之间抛出异常导致死锁,我们通常用RAII原则解决,也就是用lock_guard或者unique_lock解决。

谢谢大家!!!

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仍有未知等待探索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值