未捕获的异常,catch-all处理程序和异常说明符
到目前为止,您应该对异常如何工作有一个合理的认识。在本课中,我们将介绍一些更有趣的异常情况。
未捕获的例外情况
在过去的几个例子中,有很多情况下函数假定其调用者(或调用堆栈上的某个其他函数)将处理异常。在下面的示例中,mySqrt()假设有人将处理它抛出的异常 - 但是如果没有人真正这样做会发生什么?
这是我们的平方根程序,减去main()中的try块:
#include <iostream>
#include <cmath> // for sqrt() function
// 模块化平方根函数
double mySqrt(double x)
{
// 如果用户输入了负数,则这是错误情况
if (x < 0.0)
throw "Can not take sqrt of negative number"; // 抛出类型const char *的异常
return sqrt(x);
}
int main()
{
std::cout << "Enter a number: ";
double x;
std::cin >> x;
// 看嘛,没有例外处理!
std::cout << "The sqrt of " << x << " is " << mySqrt(x) << '\n';
return 0;
}
现在,假设用户输入-4,mySqrt(-4)引发异常。函数mySqrt()不处理异常,因此程序堆栈展开并且控制返回到main()。但是这里也没有异常处理程序,所以main()终止。此时,我们刚刚终止了我们的申请!
当main()以未处理的异常终止时,操作系统通常会通知您发生了未处理的异常错误。它是如何做到这取决于操作系统,但可能包括打印错误消息,弹出错误对话框或简单地崩溃。有些操作系统不像其他操作系统那么优雅。一般来说,这是你想要完全避免的事情!
抓住所有处理程序
现在我们发现自己陷入了一个难题:函数可能会抛出任何数据类型的异常,如果没有捕获异常,它将传播到程序的顶部并导致它终止。由于可以在不知道它们是如何实现的情况下调用函数(因此,它们可能抛出什么类型的异常),我们怎样才能防止这种情况发生?
幸运的是,C ++为我们提供了一种捕获所有类型异常的机制。这被称为全能处理程序。catch-all处理程序就像普通的catch块一样工作,除了不使用特定类型捕获,它使用省略运算符(…)作为要捕获的类型。
如果您回忆起省略号以及为什么要避免它们,之前使用省略号将任何类型的参数传递给函数。在此上下文中,它们表示任何数据类型的异常。这是一个简单的例子:
#include <iostream>
int main()
{
try
{
throw 5; //抛出一个int异常
}
catch (double x)
{
std::cout << "We caught an exception of type double: " << x << '\n';
}
catch (...) // catch-all handler
{
std::cout << "We caught an exception of an undetermined type\n";
}
}
因为int类型没有特定的异常处理程序,所以catch-all处理程序捕获此异常。此示例生成以下结果:
We caught an exception of an undetermined type
catch-all处理程序应该放在catch块链的最后。这是为了确保如果存在这些处理程序,则可以通过针对特定数据类型定制的异常处理程序捕获异常。Visual Studio强制执行此约束 - 我不确定其他编译器是否这样做。(GCC也是如此)。
通常,catch-all处理程序块保留为空:
catch(...) {} // 忽略任何意外的异常
这将捕获任何意外的异常,并防止它们从堆栈展开到程序的顶部,但没有特定的错误处理。
使用catch-all处理程序来包装main()
catch-all处理程序的一个有趣用途是包装main()的内容:
#include <iostream>
int main()
{
try
{
runGame();
}
catch(...)
{
std::cerr << "Abnormal termination\n";
}
saveState(); // 保存用户的游戏
return 1;
}
在这种情况下,如果runGame()或它调用的任何函数抛出一个未捕获的异常,该异常将展开堆栈并最终被这个catch-all处理程序捕获。这将阻止main()终止,并让我们有机会打印出我们选择的错误,然后在退出之前保存用户的状态。这对于捕获和处理可能未预料到的问题非常有用。
例外说明符
本小节应被视为可选读取,因为异常说明符在实践中很少使用,编译器不能很好地支持,而Bjarne Stroustrup(C ++的创建者)认为它们是一个失败的实验。
异常说明符是一种机制,它允许我们使用函数声明来指定函数是否可以抛出异常。这可以用于确定是否需要将函数调用放在try块中。
有三种类型的异常说明符,所有这些都使用了所谓的throw(…)语法。
首先,我们可以使用一个空的throw语句来表示函数不会抛出任何异常:
int doSomething() throw(); // 不会抛出异常
请注意,doSomething()仍然可以使用异常,只要它们在内部处理即可。使用throw()声明的任何函数都应该导致程序立即终止,如果它试图在自身之外抛出异常,但实现是不稳定的。
其次,我们可以使用特定的throw语句来表示函数可能抛出特定类型的异常:
int doSomething() throw(double); // 可能抛出一double
最后,我们可以使用catch-all throw语句来表示函数可能抛出一个未指定类型的异常:
int doSomething() throw(...); // 可能抛出任何东西
由于编译器实现不完整,异常说明符更像是意图语句而不是保证,与模板函数不兼容,以及大多数C ++程序员不知道它们存在的事实,我建议你不要使用异常说明符。