关键是迷失后的补救,物也好人也好~~~
什么时候会迷失
当异常被引发的时候,有两种情况会导致问题。
- 意外异常
- 未捕获异常
在c++98的时候,引入了一个叫异常规范的概念,指通过函数的定义来指出这个函数可能会引发什么异常,让函数的用户知道要去捕获哪些异常。下面是原型以及使用的一个例子:
1 | double Argh(double,double) throw(out_of_bounds); |
如果引发的异常没有和规范列表中的某种异常相匹配(继承层次结构中,类类型与这个类及派生类的对象匹配),也就是说触发异常的类型不在刚刚函数原型中throw定义的类型之中,这种情况就叫做意外异常。
(值得一提的是,c++11已经摒弃使用异常规范,但还保留着。主要原因是在原则上异常规范应包含函数调用的其他函数引发的异常,比如说a调用b,则a的异常规范不仅要有自己的异常也要有b的。这就使得程序员需要知道的东西更多更复杂了,如果使用的是老式商业库,并没有提供函数规范,且不是你自己写的,你也很难知道可能会引发什么异常。这使得这种异常规范机制处理起来很复杂。)
然后如果不在函数中(应该指main函数)或在没有异常规范的函数中引发了异常,且没有相应的try-catch捕获时,这时候就是未捕获异常了。但按我的理解,我觉得如果引发的异常在函数规范中,但是没有对应的catch应该也属于未捕获异常吧。
迷失了以后会怎么办
未捕获异常
之前的理解以为,未捕获异常触发时,由于函数没有找到对应解决方案,所以导致程序立即异常停止了。
其实不然,未捕获异常触发时,首先会调用terminate()函数,然后默认情况下,terminate()调用了abort()函数,也就我们熟知的导致程序立即异常终止。那可以修改terminate()默认的行为吗,答案是可以的。通过set_terminate()函数来修改,首先先看看这个函数的定义。
1 | typedef void (*terminate_handler)(); |
其中terminate_handler是指向没有参数和返回值的函数指针,
下面是一个修改默认行为的例子:
1 | #include <exception> |
这时候触发未捕获异常时,会打印一条信息,然后调用exit()函数,设置退出状态值为5.
意外异常
未捕获异常和意外异常这么相似,那触发时也是调用terminate()吗?
并不是,当意外异常时,函数会首先调用unexpected(),然后unexpected()默认再调用terminate()。
(意外异常发生->unexpected()->terminate()(默认情况下))
同样也可以修改unexpected()的默认行为,通过set_unexpected(),但是比起set_terminate(),这次的set_unexpected()修改行为更加严格。惯例先看一下原型。
1 | typedef void (*unexpected_handler)(); |
其中unexpected_handler可以是以下的函数:
- 通过调用terminate()(默认行为)、abort()或exit()来终止程序;
- 引发异常。
而引发异常的结果取决于 unexpected_handler函数中我们重新引发的异常 与 原来引发意外异常的函数的异常规范:
- 如果新引发的异常与原来的异常规范匹配,则程序将寻找与新引发的异常匹配的catch块。这种方法将用预期的异常取代意外异常;
- 如果新引发的异常与原来的异常规范不匹配,且异常规范中没有包括std::bad_exception类型,则程序将调用terminate()。其中bad_exception是从exception派生而来,声明位于exception文件中。
- 如果新引发的异常与原来的异常规范不匹配,且原来的异常规范中包含了std::bad_exception类型,则不匹配的异常将会被std::bad_exception异常所取代。
如果要捕获所有的异常,可以这样做:
1 | #include <exception> |
总而言之,在异常规范那里添加bad_exception类型,即使重新引发的异常不匹配,也会引发bad_exception异常。