异常处理
-
c++针对程序中出现的各类错误的处理机制,包含头: <exception>, <stdexcept>, <new>等
-
关键字:try(可能存在错误的代码块), throw(出现错误时抛出), catch(捕获throw抛出的对应类型的异常)
-
常见用法
- 代码块中直接抛出异常,方括号中的异常对象形参当在catch块中用不到时可以省略,也可用引用替代
try{ //do something if(...) throw excp; //1)with a throw statement 2)from function //do other something } catch(data_type [e]){ //do something }
- 由某函数中抛出异常,被调函数中抛出的异常若没有处理则会返回给主调函数,若依旧没有处理则会继续向上返回给更上层的主调函数,直到main函数,由主调函数来捕获异常并处理,若main函数中依旧没有进行处理则作为未经处理的异常终止程序
void function(...){ //do something if(...) throw excp; } try{ function(...); //use function } catch(data_type [excp]){ //do something }
以上即为异常处理的两种常见格式,主要流程都是执行try块中的语句,出现错误时抛出异常,catch负责捕获相应类型的异常并进行处理,注意在try块中当错误发生抛出异常后,try块剩余的代码将不会被执行,直接跳转到catch进行类型匹配,若没有匹配的catch则将作为未经处理的异常而终止程序
-
什么时候用异常处理?
- 简单问题简单处理,复杂问题异常处理
- 即当处理相关错误较为容易且代码行数较少时采用简单处理的手段,如使用if进行判断并进行相应处理(如判断除数是否为零,为零时如何处理),复杂的问题则一律交给异常处理(如数组越界,内存空间分配失败等异常)
异常类
-
即用于异常处理的类,即携带异常信息并由throw负责抛出的类,通常继承于标准库的异常基类exception类(包含头<exception>中)派生,部分时标准库中已经定义好的异常类,也可以自定义异常类用于进行特定的处理,下面是一些标准库中的异常类(均派生于exception)
- <stdexcpt>中:
- invalid_argument //实参不被接受
- domain_error //对象超长string/vector
- length_error //成员越界
- out_of_range
- future_error
- range_error
- overflow_error
- underflow_error
- regex_error
- tx_exception
- <exception>中:
- bad_typeid //调用typeid类时给出不存在类型的参数
- bad_cast //dynamic_cast引用转换失败时抛出
- bad_weak_ptr
- bad_function_call
- bad_alloc //动态分配空间失败时抛出
- bad_exception
以上仅对常用异常类进行介绍,实际上可根据程序调试终止时弹出的未处理的异常的信息知道当前抛出的异常的类型,而自定义异常类时尽量继承于exception标准异常基类或者其他由exception派生的标准异常类,这些类一般不需要特定的构造函数参数,但基本上都可以接受一个字符串作为参数,该字符串是对该异常的描述,而其中的what成员函数(定义在exception中的虚函数)可以返回对该异常的描述
- <stdexcpt>中:
-
异常捕获的次序:注意到在一个try块中可能存在多个错误的发生,也就可能throw多个异常类,当throw的异常类类型没有继承关系(即完全不同)时,不需要考虑catch的先后捕获次序,而当throw的多个异常类之间存在继承关系时则需要先catch派生的异常类,最后catch基类的异常类,如:
- 无继承关系的多个异常,无需考虑catch的先后次序,每个catch只针对自己相应的异常类型进行处理
try{ throw (type1) excp1; throw (type2) excp2; } catch(type1){ //... } catch(type2){ //... }
- 存在继承关系的多个异常类
try{ throw (exception) excp1; throw (out_of_range) excp2; } catch(exception){ //... } catch(out_of_range){ //... }
注意到out_of_range是exception的派生类,故此无论抛出的是何种异常,由于catch总是由前向后依次比对异常类型的,由于派生类的对象可以隐式的转化为基类的对象,故此第一个catch对上面两个异常均能正常捕获(捕获out_of_range对象时将其隐式转化为exception),而下放专门针对第二个out_of_range的catch就失去了意义,故此当抛出的多个异常之间存在继承与派生的关系时,应当先catch派生类,然后在catch基类
noexcept关键字
-
主要有两个用途,一作声明标识符,二作运算符
-
声明:该关键字用于函数形参列表后,用于显式声明该函数不会抛出异常,使编译器在编译该函数时能够进行相应的优化,声明格式有两种:
- void function(…) noexcept {…}
- void function(…) noexcept(true) {…} [ 0 ] ^{[0]} [0]
对应的,当没有显式声明函数为不会抛出异常的函数时,默认为可能抛出异常,即等价于下面的显式声明会或可能抛出异常的函数:
- void function(…) noexcept(false) {…}
- <==> void function(…) {…}
-
作运算符时用于判断某函数是否存在抛出异常的可能,返回值为bool值,true表示该函数不会抛出异常false表示可能会,如:
- noexcept(function());
注[0]:noexcept后括号中的bool表达式可以为任何可以隐式转换为bool值的表达式,如:noexcept(1), (1-1), (1+1), (1234)等,不过建议直接使用bool值表示
注1:使用noexcept声明为保证不会抛出异常的函数在实现时也可以强行抛出异常,但是这种情况下抛出的异常将不会被捕获,而是直接导致程序终止
注2:不同于类中的常函数const,noexcept关键字不能用于函数重载
异常传播
-
当多个函数层次调用时,抛出的异常会从抛出异常的函数开始依次比对是否有相应的catch来处理该异常,如果有,则跳转执行相应处理并返回上层函数依次执行上层函数的剩余代码,如果没有,则终止当前函数的余下代码执行,将抛出的异常交由上层主调函数,由主调函数依次匹配相应的catch来处理该异常,如果有,则同上述处理一致,如果没有,则同上述处理一致,始终没有匹配catch的异常会被一直返回给上层主调函数,最后直到main函数,如果main函数也没有相应catch处理该异常,则程序终止,提示未经处理的异常
-
当catch在针对异常进行处理的过程中也可以再次抛出异常,有两种情况:
-
一是对所捕获到的异常再次抛出(可能是只对所捕获的异常作部分处理然后交由上层继续处理,也可能是用于告知上层存在异常的产生),固定语法为:
- throw;
即表示将所捕获到的异常再次向更高层抛出
-
二是在catch的处理过程中可能抛出其余类型的异常,即catch的执行过程中也有错误的产生,其语法为:
- throw (excp_type) excp;
-
注1:由catch中抛出的异常不需要在try块中进行,直接使用throw抛出即可,但注意由catch中抛出的异常只能交由更高层处理,无法再次在同层次的catch中进行匹配
注2:try-catch语块可以逐层嵌套使用,即一个try块中又包含了一个try-catch块,在此条件下,由内层catch重抛的异常则交由外层catch进行匹配处理,同时内层无法处理的异常也交由外层负责