【C++】特别的程序错误处理方式——异常机制
各位大佬好,我是落羽!一个坚持不断学习进步的学生。
如果您觉得我的文章还不错,欢迎多多互三分享交流,一起学习进步!
也欢迎关注我的blog主页: 落羽的落羽
文章目录
- 一、异常的概念
- 二、异常的抛出与捕获
- 1. 异常抛出与捕获机制
- 2. 栈展开
- 3. 查找匹配的处理代码
- 4. 异常重新抛出
- 5. 异常的安全问题
一、异常的概念
异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理,异常使得我们能够将问题的检测与解决问题的过程分开,程序的一部分负责检测问题的出现,然后解决问题的任务传递给程序的另⼀部分,检测环节无须知道问题的处理模块的所有细节。
C语言中,主要通过错误码的形式处理错误,错误码本质就是对错误信息进行分类编号,拿到错误码以后还要去查询错误信息,比较麻烦。异常时抛出一个对象,这个对象可以函数更全面的各种信息。
二、异常的抛出与捕获
1. 异常抛出与捕获机制
程序出现问题时,我们通过抛出(throw)一个对象来引发一个异常,该对象的类型以及当前的函数调用链决定了应该由哪个捕获(catch)的处理代码来处理该异常。throw抛出异常后,throw后面的语句将不再执行,要在当前这个函数调用链中寻找类型匹配的catch异常捕获模块,catch可能是同一函数内的一个局部catch,也可以是调用链中另一个函数的catch,主函数中还找不到就会终止程序。如果有多个catch匹配,使用离抛出异常位置最近的那一个。代码就从找到的catch处继续执行。
根据抛出异常对象的类型不同,我们可以人为控制catch内处理异常的方式,比如打印错误信息、修改数据等等方式。而不会使程序结束。
下面这段代码举例,一个除法函数,如果被除数为0则抛出异常,异常对象是一个含错误信息的字符串。下面有一个能捕获string类的catch语句,如果抛出了这个string就能捕获到,然后打印出这个错误信息提醒用户。在这个过程中程序是正常运行的,如果是C语言的错误码处理方式,这种情形下就会导致程序直接中断。
double Div(int a, int b)
{try{if (b == 0){string s("被除数为0,错误!");throw s;}else{return (double)a / (double)b;}}catch (string& s){cout << s << endl;}return 0;
}
还需要注意:
- 抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象可能是一个临时对象。这个拷贝对象会在catch语句后销毁。
- 一旦程序开始执行异常处理,沿着函数调用链的创建的对象都将被销毁。
2. 栈展开
刚才说了,抛出异常后,程序将暂停当前函数的执行,开始寻找匹配的catch语句。首先检查throw本身是否在try语句内部,如果在则查找匹配的catch语句,如果有匹配的就跳到catch处执行。
如果当前函数中没有try/catch语句或catch不匹配,则退出当前函数,销毁当前函数栈帧,继续在外层函数调用链中查找,这个查找catch的过程称为栈展开。
如果到达主函数,依旧没有找到匹配的catch,程序就会调用标准库的terminate函数终止程序。
3. 查找匹配的处理代码
一般情况下抛出对象类型和catch是能完全匹配的。但是还有一些特殊情况,允许从非常量向常量的类型转换(非const转换为const)、函数被转换为函数指针、派生类转换为基类。派生类转换为基类是最实用的,实际项目中很多都是这样设计的。
main函数中还没有匹配到catch的话会终止程序,一般情况下我们都是不希望程序终止的,所以一般main函数中最后会写一个catch{...}
,这个catch可以捕获任意类型的异常,但是不知道异常对象是什么。有时候我们使用软件会显示“未知异常”,可能就是执行了这个catch语句。
4. 异常重新抛出
有时catch到一个异常对象时,需要对错误进行分类,比如某种错误类型需要进行特殊处理再抛出,只需要在处理完成后再写一个throw;
,就完成了异常的再次抛出
5. 异常的安全问题
异常抛出后,后面的代码就不再执行,前面申请了的资源(内存、锁等),后面需要进行释放,但是中间可能会抛异常,就会导致资源没有释放,引发了资源泄漏,产生安全性的问题。中间我们需要捕获异常,释放资源后面再重新抛出,十分麻烦。下一篇文章我们学习智能指针,就可以解决这个问题。
其次析构函数中,如果抛出异常也要谨慎处理,比如析构函数要释放10个资源,释放到第5个时抛出异常,则也需要捕获处理,否则后面的5个资源就没释放,也资源泄漏了。
除此之外,C++11中新增了一个关键字noexcept,函数参数列表后面写上noexcept表示这个函数不会抛出异常,不写就表示可能会抛出异常。
这就是为什么C++11后STL的一些容器接口写了noexcept,这种接口一定不会抛异常,如begin()、end()、size();有的接口就可能抛异常,如push、insert等。
noexcept(表达式)
还可以作为运算符检测一个表达式会不会抛出异常,可能则返回false,不会抛异常就返回true。