异常
# 前言
异常是一种处理错误的方式。当一个函数发现自己无法处理的错误时就可以抛出异常,让函数的直接或者间接调用者处理这个函数
throw:当问题出现时,程序会抛出一个异常。通过throw关键字来实现
catch:在想要处理问题的地方,通过异常处理程序捕获异常,可以有多个catch捕获
try:try块中的代码标识将被激活特定的异常。后面通常跟这一个或者多个catch块
如果有一个块中抛出异常的话,捕获异常的方法是try和catch关键字。try块中放置可能抛出异常的代码。try块中的代码称为保护代码。
# 异常的使用
# 异常的抛出和捕获
异常的抛出和匹配的原则
异常是通过抛出对象而引发的,该对象的类型决定要引发哪一个catch的处理代码
被选中的处理代码是调用链中和该对象类型匹配并且距离抛出异常位置最近的哪一个
抛出异常对象后,会生成一个异常对象的拷贝,因为抛出的异常对象有可能是一个临时对象。所以会生成一个临时对象,这个拷贝的临时对象会在被catch之后销毁
catch(...)可以捕获各种类型的异常
扫描二维码关注公众号,回复: 5283664 查看本文章实际中抛出和匹配的原则有着一个例外。可以抛出派生类对象,使用基类捕获(实际中经常使用)
在函数调用栈中异常栈展开的原则
首先检查throw本身是否在try的内部,如果是的话就查找匹配的catch语句,如果有匹配的就调用调用catch的处理代码进行处理
没有的话,那就退出当前的函数栈,继续在调用函数的栈中进行查找匹配的catch
如果到达main函数的栈,还没有找到匹配的catch,那就终止程序
找到匹配的catch子句并处理之后,需继续沿着catch子句后面继续执行
【注意】:
所以在实际情况当中我们都要加上一个catch(...)捕获任意类型的异常,否则当有异常没有被捕获到的时候,程序就会终止
# 异常的重新抛出
有可能单个的catch不能完全处理一个异常,在进行一些校正处理之后,希望再次交给更外层的调用链函数来处理,catch则可以重新抛出异常交给上层的函数进行处理
例:
#include <iostream> #include <vector> using namespace std; double Division(int x, int y) { if (y == 0) { throw "Divison by zero condition"; } return (double)x / (double)y; } void Func() { int* array = new int[10]; try { int x, y; std::cin >> x >> y; std::cout << Division(x, y) << std::endl; } catch(...) { cout << "delete[]" << array << std::endl; delete[] array; throw; } std::cout << "delete[]" << array << std::endl; delete[] array; } int main() { try { Func(); } catch(const char* errmsg) { std::cout << errmsg <<std::endl; } catch(...) { std::cout << "unkown exception!" << std::endl; } return 0; }
# 异常安全
构造函数通常完成对象的初始化,不要再构造函数中进行抛异常,有可能导致对象没有初始化完全
析构函数通常完成资源的清理,不要再析构函数中进行抛异常,有可能导致资源泄露
C++异常会导致执行流的乱跳转,所以就会导致资源泄露的问题。比如,在new和delete中抛出异常,导致内存泄露,在lock和unlock中抛出异常导致死锁的问题。
# 异常的规范
异常规格说明的目的是为了让函数使用者直到该函数可以抛出的异常有哪些
在函数的后面接throw(类型, ...),列出这个函数可能抛出的所有异常的类型
函数的后面接上throw(),表示这个函数不会抛出异常
若无异常接口声明表示这个函数可能抛出任何类型的异常
例:
void Func() throw(const char*) //表明这个函数可能抛出const char*类型的异常 void Func() throw() //表明这个函数不会抛出异常 void Func() //表明这个函数可能抛出任何类型的异常
# 异常的标准体系
自己定义的异常标准体系
对于一个公司来说的话一般都是自己定义一个异常标准体系。这样的话大家抛出的都是派生类对象,然后捕获一个基类就行了
C++标准库异常体系
以父子类层次结构组织起来的
他们都是采用继承的方式实现异常体系的,并且抛出对象的时候一般都是派生类对象采用基类对象来进行捕获异常
# 异常的优缺点
优点:
异常对象定义好了,相比于错误码可以清晰地展示出各种的错误信息,可以更好的定位程序的bug
对于错误码的方式有着一个极大的缺点,那就是在函数调用链中,深层次的函数返回了错误,必须要层层返回错误,最外层才能拿到错误。异常可以进行执行流的跳转
很多的第三方库都包含异常。比如boost,gtest,gmock库
很多测试框架都使用异常这样才可以进行白盒测试
部分函数使用异常可以更好的处理,比如构造函数没有返回
缺点:
异常会导致执行流的乱跳,并且运行时出错异常就会乱流
C++没有垃圾回收机制,资源需要自己进行管理,有了一场非常容易导致内存泄露,死锁等异常安全问题
C++标准库异常体系定义的不好,导致大家各自定义各自的异常体系,非常的混乱
异常尽量规范使用,否则后果不堪设想,随意抛异常,外层捕获的用户苦不堪言。
异常规范
抛出异常类型都继承自一个基类
函数是否抛出异常,抛出什么异常都采用Func() throw()的方式规范化
# 总结
异常总体来说,利大于弊,所以对于一个工程来说还是多用异常。并且OO(面向对象)的语言都是使用异常来处理错误。
大家可以看一下我写的代码:
https://github.com/YKitty/LinuxDir/tree/master/C%2B%2BCode/abnormal