C++ 异常处理机制
异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。典型的异常包括失去数据库连接以及遇到意外输入等。从去年开始有意识地开始训练自己的思维方式,希望自己可以找到一套相对通用的学习模板,目前觉得相对适用的就是what
、when
、why
和how
,关于why
看到的资料不多,下面是在笔记中记录的一段话,当时没有写下出处,它从语言设计的层面考虑了 C++中为什么添加异常,觉得很棒,分享给大家:
C++异常主要为设计容错程序提供语言级支持。
为什么需要异常?
自从 C 被发明初来,C 程序员就习惯使用错误代码,但如果一个函数通过设置一个状态变量或返回错误代码来表示一个异常状态,没有办法保证函数调用者将一定检测变量或测试错误代码。结果程序会从它遇到的异常状态继续运行,异常没有被捕获,程序立即会终止执行。由此看来异常不能忽略。 C 程序员能够仅通过 setjmp 和 longjmp 来完成与异常处理相似的功能。 但是在 C++中使用 longjmp,存在一些缺陷,当它调整堆栈时不能对局部对象调用析构函数(VC++能保证这一点,但不要依赖这一点)。 如果你需要一个方法,能够通知不可被忽略的异常状态,并且搜索栈空间(searching the stack)以找到异常处理代码并且确保局部对象的析构函数必须被调用,此时就需要使用 C++的异常处理。
接下来,来了解 C++的异常处理机制。
C++异常处理机制通过异常检测和异常处理两部分进行支持,包括:
throw
表达式,异常检测部分使用throw
表达式抛出异常。try
语句块,异常处理部分采用try
语句块进行异常处理,try
语句块以关键字try
开始,并以一个或多个catch
子句结束。try
语句块中代码抛出的异常通常会被某个catch
子句处理。因为catch
子句“处理”异常,所以它们也被称作异常处理代码(exception handler)。一套异常类,用于在
throw
表达式和相关的catch
子句之间传递异常的具体信息,C++中提供了标准exception
库
简单的异常处理
在 C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在用户编写的程序中使用,它们分别定义在 4 个头文件中:
exception
头文件定义了最通用的异常类 exception。它只报告异常的发生,不提供任何额外信息。stdexcept
头文件定义了几种常用的异常类
new
头文件定义了bad_alloc
异常类型,默认,如果new
不能分配所要求的内存空间,它会抛出一个类型为bad_alloc
的异常。我们可以改变使用 new 的方式来阻止它抛出异常:
type_info
头文件定义了bad_cast
异常类型,dynamic_cast<type&>(e)
如果转换目标是引用类型并且失败了,则dynamic_cast
运算符将抛出一个bad_cast
异常。
函数在寻找处理代码的过程中退出
当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的 catch 子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的 catch 子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的 catch 子句为止。如果最终还是没能找到任何匹配的 catch 子句,程序转到名为 terminate 的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。
-- 摘自《C++Primer》5.6.2 节
异常规格
异常规格对函数定义进行限定,指出其可能引发那些类型的异常,同时提醒程序员此处可能抛出异常应该使用try-catch
和处理程序,其由 throw 关键字和异常类型列表组成,异常类型括在括号中,用逗号分割。函数的异常规格不仅要包含自身触发的异常类型,还要调用其他函数可能触发的异常类型。异常规则在 C++11 中被舍弃,在 C++17 中被彻底移除,改用noexcept(true)
和noexcept(false)
,前者表示该实现无异常抛出,后者表示所有的异常。
意外异常
如果带异常规格的函数抛出了和规格列表中不匹配的异常,称之为意外异常,会调std::unexpected
,该函数默认会调用std::terminate
,用户也可以通过std::set_unexpected
设定用户自己设定的行为
unexpected_handler
函数行为遵守严格的限制:
通过调用
terminate
(默认)、abort()
、exit()
来终止程序。引发异常,结果取决于
unexpected_handler
函数所引发的异常以及引发意外异常的函数异常规范:新引发的异常和原异常规范匹配,则程序寻找与新引发异常匹配的
catch
块,该行为相当于用预期异常取代意外异常。新引发的异常和原异常规范不匹配且异常规范没有
std::bad_exception
类型,则程序将调用terminate()
。新引发的异常和原异常规范不匹配且异常规范包含
std::bad_exception
类型,则不匹配的异常将被std::bad_exception
异常所取代
未捕获异常
没有try
块或者catch
块时,异常未捕获,称之为未捕获异常。默认,将会导致程序异常终止。也可以修改程序对意外异常和未捕获异常的反应。未捕获的异常,通常不会立刻导致程序终止,程序首先会调用terminate()
此时会调用std::abort()
函数,我们可以修改terminate()
的实现来实现行为定制。相关接口定义:
假设程序抛出了一个未捕获的运行时异常,并自定义终止terminate_handler
。
参考资料
C++ Primer 中文版 第五版
版权声明: 本文为 InfoQ 作者【正向成长】的原创文章。
原文链接:【http://xie.infoq.cn/article/bb804832fca29c19dd048f5b8】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论