异常处理

对于一个被throw出的异常,根据调用链一层层向外返回(栈展开)找处理代码,也就是寻找try对应的catch,并将控制权交予这个catch对应的处理代码。当找到最外层也未找到时,直接调用std::terminate()(执行的挺慢的)终止程序。

由于可能提早退出函数,函数中创建的局部存在的对象需要被销毁,而此时折构函数与其他等等会自动被调用,但如果涉及到了构造函数内部或者动态内存,则需要我们手动进行处理,以确保正确的销毁。

但如果折构函数本身抛出了异常,那么后续的销毁就不能正常的进行了,所以程序一般需要保证折构函数内部的异常要在折构函数内部处理完成。

异常对象

实际上,当我们抛出一个异常表达式时,会通过这个异常抛出语句来拷贝初始化一个异常对象,这个异常对象会存在于编译器管理的空间中,在全局都能访问,并在异常被处理后被销毁。

由于要通过这个表达式进行初始化,因此表达式必须是完全类型的,类的话必须拥有可访问的折构函数和拷贝或移动构造函数。而函数和数组将会被转为指针类型。

另外,这个表达式不应该是指向局部的指针。且该表达式拷贝时的类型会根据静态编译时的类型而决定,不会因为指向派生类而变为派生类,所以可能会切掉派生部分。

catch

catch就像一个函数一样,每次抛出异常,就像是调用catch这个函数,而最终选择哪一个catch来处理,则是根据catch的参数和调用时也就是抛出时表达式的类型。其像函数一样可以使左值引用。一切catch中的操作真的就可以完全当catch时一个函数,除了没有右值引用。

当进行“函数匹配”时,只允许三种转换,const,派生与基,数组与函数转为指针。

当catch匹配的类型中存在继承关系,则应该把最派生的类放前面,最基的类放后面。

catch(…)可以捕获所有异常。

重新抛出

有的时候,多个catch会一起用于处理一个错误,如派生类部分处理完交给基类继续处理。此时我们就可以通过throw;来重新抛出,为了能让在这个catch中处理的保留到下面一个catch中,应使用引用。

初始化列表的try

由于初始化列表不是函数体内的东西,所有写这个构造函数的时候不能将其异常处理。

此时可以使用函数try语句块。

returntype fun(...)try:初始化列表{}catch...returntype\ fun(...)try:初始化列表\{\}catch...

noexcept

我们可以使用noexcept来声明函数不会抛出异常,并且,一旦定义就必须在所有声明和定义处声明noexcept,也可以给函数指针声明。

即便声明了noexcept,编译器也不会去检查该函数是否真的不会抛出异常,因此,一旦一个声明noexcept的函数抛出了异常,程序就会立即终止。

因此一般用作确认函数不会抛出异常或者我们根本不知道该如何处理异常。

老版本还有throw(),与noexcept作用与用法一致。

实际上noexcept也有参数,可以以一个bool作为实参,默认为true。

可以与noexcept运算符联用,noexcept(函数),当且仅当该函数里可能会调用的所有函数都被声明为noexcept的时候会返回为true,因此给某一个函数定义noexcept时可以使用noexcept(noexcept(g()))

当一个指针被声明为noexcept后只能指向noexcept的函数,若没有则都可以指向。

而若是一个虚函数声明了noexcept则所有的派生都必须noexcept,但派生可以在基类不noexcept的情况下声明为noexcept的。

拷贝控制函数在合成时也会自动生成一个异常说明,这会根据其所有成员和基类的操作是否承诺了是否不会抛出异常相关。

折构函数的异常标识和合成折构函数相同。

异常类层次

一般情况下我们都会继承这些异常类来实现自己的异常类。

标准库的异常类仅仅定义了拷贝构造函数、拷贝赋值运算符、徐折构函数和一个名为what的虚成员函数,what返回一个const char*(表示异常的一些信息,也就是throw的时候写的字符串)。


异常处理
https://lhish.github.io/project/hide/异常处理/
作者
lhy
发布于
2024年6月30日
许可协议