c++中的异常处理

c++是一门复杂的语言,程序的运行环境也是千奇百怪。因此总会有异常出现,本文主要就是讨论一下c++程序中的异常处理问题。

异常对象

讲到异常,这里就要说明一下,什么是异常?在c++中,异常就是指异常对象。而异常对象是异常类的实例化,异常类是我们定义的用来表示程序错误信息的数据抽象。下图便是标准库异常类的继承体系。 [exception](/assets/img/exception/exception.jpg)

由上图我们可以看到,异常类的继承体系中基类是exception类。整个关系可以看成是按照层次关系组织的,层次越低,表示的异常情况就越特殊。

一般情况下,每个异常类都定义的有: 1. 拷贝构造函数; 2. 拷贝赋值运算符; 3. 虚析构函数; 4. 一个名叫what的虚成员函数。没有参数,返回一个const char*的指针指向一个以null结尾的字符数组,并且确保不会抛出任何异常。

函数try语句块

一般异常处理部分使用try语句块处理异常,try语句块以关键字try开始,并以一个或多个catch子句结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为catch子句“处理”异常,所以它们也被称作异常处理代码。其一般形式如下:

1
2
3
4
5
6
try{
//program-statements
}
catch(exception-declaration){
//handler-statements
}

抛出异常(throw)

抛出异常一般指的是抛出一个异常对象。一般使用如下语句进行异常的抛出。

throw exception1;   //其中,exception1是一个异常对象。

这样的形式特别像return语句。它们的作用机理也会有些许相似,都提供一个待接收的对象。

捕获异常(catch)

捕获异常也是指捕获一个异常对象。捕获的过程也就是参数匹配的过程。一般捕获异常使用如下形式进行:

1
2
3
4
//exception-declaration通常与函数形参的声明形式一样,可以与throw抛出的异常对象进行参数匹配。
catch(exception-declaration){
//handler-statements
}
### 异常匹配过程解析 通常最完美的一个异常处理过程如下: - 如果在一个try语句块内有异常抛出,则检查与该try块关联的catch子句,如果找到了匹配的catch,就使用该catch处理异常。然后执行完这个catch子句后,找到与try块关联的最后一个catch子句之后的点,并从这里继续执行。

但是一般情况下,并不会如此顺利,往往一个更完整的异常匹配过程如下: - 对于在一个try语句块内有异常抛出,则检查与该try块关联的catch子句,如果找到了匹配的catch,就使用该catch处理异常。否则,如果该try语句嵌套在其他try块中,则继续检查与外层try匹配的catch子句。如果仍然没有找到匹配的catch,则退出当前这个主调函数,如果调用语句也是在一个try语句块内,继续在调用了刚刚退出的这个函数的try代码块中寻找。直到找到与之匹配的catch,并使用该catch处理异常。然后执行完这个catch子句后,找到与这个catch对应的try块关联的最后一个catch子句之后的点,并从这里继续执行。上述过程也叫作栈展开

栈展开过程沿着嵌套函数的调用链不断查找,直到找到了与异常匹配的catch子句为止;或者也可能一直没找到匹配的catch语句,则退出主函数后查找过程终止。(如果找不到匹配的catch时,程序会调用标准库函数terminate终止程序)

异常捕获中的参数匹配规则

上文说过,catch语句的捕获与throw语句的抛出异常之间会进行参数匹配。匹配规则类似函数的形参与实参的匹配,但是也不是完全相同。

一般而言要求异常的类型与catch声明的类型是精确匹配的,匹配时一般只有如下三种类型转换可用: 1. 允许从非常量向常量的类型转换,也就是说,一条非常量1对象的throw语句可以匹配一个接受常量引用的catch语句; 2. 允许从派生类向基类的类型转换; 3. 数组被转换成指向数组(元素)类型的指针,函数被转换成指向该函数类型的指针。

除此之外,包括标准算数类型的转换和类类型转换在内,其他所有转换规则都不能在匹配catch的过程中使用。

参考资料

  • [1] C++ Primer(第5版)

c++中的异常处理
http://line.com/2018/11/03/2018-11-04-cpp-exception/
作者
Line
发布于
2018年11月3日
许可协议