函数try块
本节阅读量:Try和catch块在大多数情况下都工作得很好,但有一种特殊情况下它们是不够的。考虑以下示例:
|
|
在上面的示例中,派生类B调用基类构造函数A,该构造函数可能抛出异常。由于对象b的创建已放在try块中(在函数main()中),因此如果a抛出异常,main的try块将捕获它。因此,该程序打印:
|
|
但如果我们想在B内部捕获异常呢?在调用B构造函数的函数体之前,通过成员初始值设定项列表调用基构造函数A。没有办法围绕它设置对应的try块。
在这种情况下,我们必须使用稍微修改的try块,称为函数try块。
函数try块
函数try块被设计为允许您围绕整个函数体建立异常处理程序,而不是围绕代码块。
函数try块的语法有点难以描述,因此我们将通过示例演示:
|
|
运行此程序时,它会生成输出:
|
|
让我们更详细地检查这个程序。
首先,注意在成员初始值设定项列表之前添加了try关键字。这表示该点之后的所有内容(直到函数结束)都应被视为在try块内。
其次,请注意,关联的catch块与整个函数处于相同的缩进级别。在try关键字和函数体末尾之间引发的任何异常都可以在此处捕获。
当上面的程序运行时,变量b开始构造,它调用b的构造函数(使用函数try)。B的构造函数调用A的构造函数,然后引发异常。由于A的构造函数不处理该异常,因此该异常向上传播到B的构造函数,在那里它被B的构造函数的函数级catch捕获。catch块打印“Exception caught”,然后将当前异常重新抛出,该异常由main()中的catch块捕获,后者打印“Oops”。
最佳实践
当需要构造函数来处理成员初始值设定项列表中引发的异常时,请使用函数try块。
函数catch块的限制
对于常规catch块(在函数内),我们有三个选项:我们可以抛出新的异常、重新抛出当前异常或解决异常(通过return语句或让控制流执行到catch块的末尾)。
构造函数的函数级catch块必须抛出新异常或当前异常——不允许它们解决异常!也不允许reurn语句,到达catch块的末尾将隐式重新抛出异常。
析构函数的函数级catch块可以抛出新异常或当前异常,或者通过return语句来解决异常。到达catch块的末尾将隐式重新抛出异常。
其他函数的函数级catch块可以抛出新异常或当前异常,或者通过return语句来解决异常。到达catch块的末尾将隐式解决空(void)返回函数的异常,在其它返回类型的函数上将产生未定义的行为!
下表总结了函数级捕获块的限制和行为:
函数类型 | 是否可以使用return语句解决异常 | 执行到catch块结束的行为 |
---|---|---|
构造函数 | 否 | 隐式重新抛出异常 |
析构函数 | 是 | 隐式重新抛出异常 |
返回void的函数 | 是 | 解决异常 |
返回非void的函数 | 是 | 未定义的行为 |
由于catch块末尾的这种行为因函数类型而异(并且在返回非void类型的函数的情况下包括未定义的行为),因此我们建议不要让控制流到达catch块的末尾,并且始终显式抛出、重新抛出异常或使用return语句。
在上面的程序中,如果我们没有在构造函数的函数级catch块中显式重新抛出异常,则控制流将到达函数级catch。由于这是构造函数,因此将发生隐式重新抛售。结果会是一样的。
尽管函数级try块也可以与非成员函数一起使用,但通常没人这么使用,因为很少有需要这样做的情况。它们几乎专用于构造函数!
最佳实践
避免让控制流到达函数级catch块的末尾。相反,请显式抛出、重新抛出异常或显示return。
函数try块可以捕获基类异常和当前类异常
在上面的例子中,如果A或B的构造函数抛出异常,它将被B的构造函数周围的try块捕获。
在下面的示例中,我们可以看到这一点,其中我们从类B而不是类A引发异常:
|
|
我们得到相同的输出:
|
|
不要使用函数尝试清理资源
当对象的构造失败时,不会调用类的析构函数。因此,您可能会尝试使用函数try块在失败之前清理部分被分配的资源。然而,引用构造失败对象的成员被认为是未定义的行为,因此对象在catch块执行之前是“死的”。这意味着您不能使用函数在类构造失败之后尝试清理。如果要在类构造失败之后清理,请遵循清理引发异常的类的标准规则(RAII)。
函数try主要用于将异常传递到调用栈之前记录失败,或者用于更改抛出的异常类型。
