最近在看C++编程规范和C++primer,遇到了有关异常的一些内容,在此简单总结一下,以后可能会继续补充
1.异常的意义是什么?为什么要进行异常处理?
异常机制允许我们在运行时就对出现的问题进行及时处理,假如我们的程序运行过程出现错误,我们又不想让程序直接挂掉,我们就可以对可能出现的错误提前进行处理。
异常机制使我们能将问题的检测与解决过程分开,模块化设计是大家所提倡的,既然我们决定要对错误进行处理,就要写处理的逻辑代码,如果和业务逻辑写在一起会使代码非常凌乱,不利阅读和移植。
2.抛出异常和捕获异常时发生了什么?
当我们抛出一个异常时,程序就暂停在当前的执行并开始寻找与异常匹配的catch子句。
首先,当throw在一个try语句块中时,就先检查与该块关联的catch语句。如果找到就使用这个catch来处理,如果找不到并且这个try被嵌套到其他的语句块,那么就继续检查外层的catch语句,如果还是找不到,就退出当前的函数,在调用当前函数的外层函数中找,如下图。
同理如果一个异常来源一个位于try语句块中的语句,那么也是上面的嵌套调用过程。
这个过程被叫做栈展开,如果最后都没有找到匹配的catch就会调用terminate函数终止程序。
3.异常对象是什么?与其他对象有什么不同?
异常对象是一种特殊的对象,编译器使用异常抛出表达式来对异常对象进行初始化。
异常对象由于其机制的特别,位于由编译器管理的空间中。那么为什么这么做?根据我们刚才提到的栈展开规则,如果在当前catch找不到匹配的异常,就要退出当前的语句块,与此同时,这个语句块的所有局部对象就会被释放掉。如果我们抛出的是一个指向局部对象的指针,那个指针指向的就是一个被释放的内存,这肯定是错误的。
所以由编译器来创造一个特殊的空间来管理异常对象,这样才能确保无论程序调用的是哪个catch子句都能访问该空间
4.异常对象的类型由谁决定?
当我们抛出一个表达式时,catch表达式的参数的静态编译类型就决定了异常对象的类型。
5.catch语句里的参数为什么有时可以省略形参?
如果catch只需要了解异常的类型,就可以省去形参。但是假如我们要对异常对象进行处理就需要保留形参。其实,我们可以把catch(参数)语句看做一个函数,当进入一个catch后,异常对象被初始化,如果catch括号里面的是非引用类型,则异常对象是一个局部副本,如果是引用类型,则参数就是一个别名。
6.既然说catch语句既然和函数匹配相似,那是不是有什么不同?
虽然catch的执行有点像函数匹配,但是catch明显要严格很多。只允许3种转换匹配。
1.非常量到常量的类型转换
2.派生类到基类的转换
3.数组转换为指向数组类的指针
另外,如果catch的参数是一个基类类型,则可以用派生类类型来对其初始化,不过这时候就会发生切片(简单理解就是,用子类对象对基类对象进行赋值运算时会将子类对象独有的(非继承的部分)函数和变量自动“切去”的机制。只留下子类继承来的基类原有的“切片”来对基类的对象进行赋值。)
注意catch的执行和switch case类似,自上向下顺序执行,由于我们说道catch可以匹配一个基类类型的所有派生类类型,所以要把最专门详细的匹配放在前面,越宽泛的放在后面。
7.构造函数初始化列表的异常可以捕获么?有什么特别的?
任何地方都可能出现异常,包括类的构造函数初始化数据的时候,这种情况下我们无法用普通的try catch形式来捕获,我们必须要将构造写成函数try语句块。
class TryClass { int* m_pArray; public: TryClass ( int m_pNum ); ~ TryClass (); }; TryClass :: TryClass ( int pNum ) try: pArray ( new int[pNum ] ) { cout << pNum << endl; } catch ( bad_alloc& error ) { cout << "alloc error" << endl; };
如果当前语句的catch不能处理或者不想处理捕获到的异常可以通过throw;来重新抛出,这个异常会被传到外层的catch中去。
如果想捕获所有异常,使用catch(...)语句即可。
下一篇博客给大家分析一下C++编程规范101条 C++编程规范解析 62条 不要允许异常跨越模块边界传播