C++基础教程面向对象(学习笔记(76))

未捕获的异常,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 ++程序员不知道它们存在的事实,我建议你不要使用异常说明符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值