C++ 头文件系列(exception)
内容概览
一图解百问,但是有些地方我们需要特别指出:
- 类型在这里指通过typedef重定义的,例如函数类型、指针类型等。
exception_ptr
在标准中是未定义具体实现的,因此它可能是类也可能是类型。uncaught_exception()
和uncaught_exceptions()
是两个不同的函数,分别 判断是否有未处理异常、返回未处理异常的个数 。 其中,uncaught_exceptions()
函数仅在C++14开始出现。
仔细观察上图,你就能发现标准库给我们提供了以下几个方面的异常处理支持:
- 未捕获异常处理
- 异常嵌套
- 异常重抛
未捕获异常处理
其实未捕获异常可以分为两类,一类有关try-catch块,另一类有关dynamic-exception-specification(动态异常指定)。
try-catch
当抛出的异常未被catch块捕获时,标准库的terminate()
函数会被自动调用,默认情况下该函数调用abort()
函数非正常终止程序。 非正常终止是什么意思呢? 这里我们引用标准的一句话来解释:
The program is terminated without destroying any object and without calling any of the functions passed to atexit or at_quick_exit.
大概意思是说,非正常终止不会析构任何对象,也不会调用任何通过atexit 或者 at_quick_exit注册的处理函数。
很明白的,程序的资源释放会成为一个严重的问题。 因此,标准库提供了set_terminate()
、get_terminate()
来帮助用户获取和设置处理器做一些必要的清理工作 、让用户来决定是否终止程序。
dynamic-exception-specification(C++11中已废弃)
在C++11之前,函数签名中还可以指定抛出的异常类型(如果有):
void function() throw(int) {...}
如上,如果该function函数抛出了任何非int异常类型,unexpected()
函数也会被自动调用。 为此,C++标准甚至特地规定了一个bad_exception
异常类来表示这种情况。 我们把这类情况也称为未捕获,该函数默认调用terminate()函数,不再赘述。
但是,但是,该特性在实践中被证明非常“鸡肋”, 因此从C++11开始被标记为废弃。
异常嵌套
为了支持异常嵌套,标准库提供了三个积木: nested_exception异常类 、throw_with_nested函数 、rethrow_if_nested函数。
如何嵌套
标准定义了一个异常类nested_exception,这个类非常特殊,它没有继承自通用的异常基类exception。 标准指出,该类是为了继承之用,以配合其它两个函数实现嵌套异常机制。
如何构造嵌套
答:使用函数构造并抛出,以下是模版函数原型:
template <class T> [[noreturn]] void throw_with_nested(T&& t);
该函数将当前异常类(正在处理的异常类)与传入类型构造成一个嵌套类型, 当前异常类为nested-exception,传入类型为outer-exception。
如何解嵌套
答: 使用函数解嵌套并重抛,以下是函数原型:
template <class E> void rethrow_if_nested(const E& e);
如果传入异常类型为嵌套异常,该函数会抛出被嵌套的异常。
个人感觉
看样子,C++标准是打算提供一个方便的异常嵌套模型供开发者使用,但是给我的感觉确非常别扭。 从嵌套构造上来讲,嵌套异常的构造方式非常模糊,需要结合当前上下文,传入一个outer-exception类型也很不舒服;从解嵌套的方法上来讲,抛出似乎是比较合理的获得方式,但该函数名(rethrow_if_nested)不够友好----抛出?是抛出该嵌套异常呢还是被嵌套异常?
异常重抛
重抛的支持非常简单,调用函数:
void rethrow_exception(exception_ptr p);
但是注意,传参是exception_ptr类型,你想要重抛异常的话还需要经过一次类型转换(通过make_exception_ptr()函数)。 -……-怪不得C++总被人诟病别扭。。。