CPP异常处理
Mar 28, 2016
对于可以预计的问题,相对于原始的if判断
与返回值
方法,异常处理机制的主要目的,是「把问题的检测
与处理
分离」,相互独立以后,可以降低耦合度,且相互不需要知道内部细节。
栈展开(stack unwinding)
每个函数/代码块(function/block)被调用时,会把局部变量压栈。
然而当异常被throw后,当前栈就直接释放,搜寻是否有匹配的catch。
如果当前的try块的catch不能匹配,则再退栈
,返回上一层函数,搜索匹配的catch。直到有catch接住这个异常为止。
这个过程就是栈展开。
我们需要注意的是:
- 退栈的情况和
函数调用
完毕后的退栈情况一样,不要传出局部变量的引用或指针 - 抛出异常时,代码的行为类似于
return
,后面的代码都不执行。于是才用RAII管理资源是必须的。 - [不重要]退栈的过程中清理局部变量,会调用析构函数。所以,析构函数一般不抛异常。如果你要抛,自己处理掉。否则直接terminate。
异常对象
异常对象可以是标准库里面的exception类型,也可以是任意的类型。因为退栈,局部变量都会被销毁,这个对象要能让很多层以外的调用者知道是什么东西出了问题。
STL里面的类型大致分两种:runtime_error和logic_error
runtime主要是溢出、越界。指运行时错误。
logic主要是参数错误、范围错误。指代码的错误。
「注意」
为了满足退栈后别人还能访问到这个对象的特性,异常对象被存放在编译器管理的内存空间中。在catch语句结束后销毁。
异常的捕获
可以说,catch是一种特殊的函数。根据被抛出的异常对象的类型,编译器裁决是否符合catch函数对参数的要求。catch语句与普通的函数调用
的主要区别如下:
函数调用裁决时,允许最大限度的类型转化
。而catch则非常严格,只允许子类到父类、变量到常量、数组函数名转指针这三种类型转化。
另外:
- 异常对象的形参,和函数形参相似,也是使用「拷贝初始化」的方式初始化的。所以,这个异常类如果是自己定义的,必须小心「拷贝构造」函数。
- 和函数调用一样,
引用类型
的参数会直接操作全局的异常对象。
额外的主题
rethrowing
对于不能完全处理的异常,可以处理一部分,然后再向上抛。语法就是只写一个throw;
捕获所有异常
catch函数类型要求严格,想要一个函数接住所有,这样:catch(...)
。