20.x - 小结与测试 - 异常

异常处理提供了将错误处理或异常情况处理流程与正常流程代码解耦的一种机制。这样一来程序员就可以根据实际情况,更加自如地处理错误,同时还可以在很大程度上缓解由基于返回错误码的方式处理异常所带来的问题。

throw语句可以用来抛出一个异常。Try 语句块则会关注其块内代码是否抛出异常。一旦有异常抛出,这些异常就会被重定向到对应的catch 语句块中,不同类型的语句块可以捕获对应类型的异常并对其进行处理。默认情况下,异常只要被捕获,就可以认为是处理了。

异常会被立即处理——一旦异常被抛出,控制流会立即跳转到距离该Try语句块最近,且能够处理该异常的catch语句块。如果抛出异常的语句不位于任何try语句块中,则异常会沿着函数调用栈上送,如果在整个函数栈中都没有能够找到可用的异常处理逻辑,程序就会因为异常未处理而停止运行。

抛出的异常可以是任意类型的,包括类。

catch 语句块可以被声明为只捕获某种特定类型的异常,也可以使用省略号来声明为捕获所有类型的异常。能够捕获某种基类的 catch 语句块,也可以捕获该基类对应的派生类类型的异常。标准库中抛出的所有异常类型,都派生自 std::exception 类(位于exception头文件中)。因此,捕获 std::exception 的 catch 的语句块,可以捕获标准库中的异常。what() 成员函数可以用来确定被抛出的 std::exception 是什么。

在 catch 语句块中,还可以进一步抛出新的遗产。因为这个异常不是在 try 语句块中抛出的,所以它不会被抛出它的 catch 语句块捕获。异常可以在 catch 语句块中通过throw关键字将该异常重新抛出。不过,不要将捕获的的异常变量直接重新抛出,这可能会导致对象切片

函数中的try-block语句块使我们可以捕获发生在函数内部或成员初始化列表中发生的异常。通常只会在派生类的构造函数中使用。

在任何情况下,也不要在析构函数中抛出异常。

通过 noexcept 异常标识符可以将函数标记为不引发异常。

异常处理是有开销的。在大多数情况下,使用异常的代码会稍慢一些,而且处理异常的开销非常高。因此,我们应该只在需要处理异常的场合下使用异常,而不要在一般的错误处理时使用异常(例如处理非法输入)。