个人主页:仍有未知等待探索-CSDN博客
专题分栏:C++
目录
一、什么是异常?
异常:是指程序在运行的过程中发生的一些异常事件,如,除零溢出、数组下标越界、所要读取的文件不存在、空指针、内存不足、访问非法内存等。
在实际开发中,异常是一个类。
二、异常的抛出和捕获
异常抛出和捕获的关键字: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解决。
谢谢大家!!!