Skip to content

Latest commit

 

History

History
138 lines (93 loc) · 7.29 KB

20.4 — Uncaught exceptions and catch-all handlers.md

File metadata and controls

138 lines (93 loc) · 7.29 KB

20.4 — Uncaught exceptions and catch-all handlers

By now, you should have a reasonable idea of how exceptions work. In this lesson, we’ll cover a few more interesting exception cases.

现在你应该知道异常怎么工作了,这一节我们会研究一些更有意思的东西。

Uncaught exceptions

In the past few examples, there are quite a few cases where a function assumes its caller (or another function somewhere up the call stack) will handle the exception. In the following example, mySqrt() assumes someone will handle the exception that it throws -- but what happens if nobody actually does?

在前面的例子中,有一些函数假定它的调用者会处理异常,但是如果没人处理异常会怎么样呢?

Here’s our square root program again, minus the try block in main():

下面是一个平方根的函数,去掉了main中的tryblock

#include <iostream>
#include <cmath> // for sqrt() function
 
// A modular square root function
double mySqrt(double x)
{
    // If the user entered a negative number, this is an error condition
    if (x < 0.0)
        throw "Can not take sqrt of negative number"; // throw exception of type const char*
 
    return sqrt(x);
}
 
int main()
{
    std::cout << "Enter a number: ";
    double x;
    std::cin >> x;
 
    // Look ma, no exception handler!
    std::cout << "The sqrt of " << x << " is " << mySqrt(x) << '\n';
 
    return 0;
}

Now, let’s say the user enters -4, and mySqrt(-4) raises an exception. Function mySqrt() doesn’t handle the exception, so the program stack unwinds and control returns to main(). But there’s no exception handler here either, so main() terminates. At this point, we just terminated our application!

假设用户输入-4,然后mysqrt抛出了个异常,mysqrt没有处理这个异常,所以程序栈展开,控制交还给main,但是main也没有任何的异常处理,所以main中止了。程序也终止了。

When main() terminates with an unhandled exception, the operating system will generally notify you that an unhandled exception error has occurred. How it does this depends on the operating system, but possibilities include printing an error message, popping up an error dialog, or simply crashing. Some OSes are less graceful than others. Generally this is something you want to avoid altogether!

当main函数终止的时候,操作系统通常会通知你一个没有处理的异常发生了。它怎么做事情是依赖于操作系统的,但是大概率是打印一条错误信息到错误弹框里面,或者直接崩溃。有一些操作系统不够体面。通常你想避免这样的情况。

Catch-all handlers

And now we find ourselves in a conundrum: functions can potentially throw exceptions of any data type, and if an exception is not caught, it will propagate to the top of your program and cause it to terminate. Since it’s possible to call functions without knowing how they are even implemented (and thus, what type of exceptions they may throw), how can we possibly prevent this from happening?

我们现在遇到了一个难题:函数可能抛出任何类型的异常,如果一个异常没被处理,那么他会传播向上,导致你的程序崩溃。因为调用一个不知道抛出什么异常的函数是极有可能的,我们怎么才能阻止因为这样而程序崩溃呢?

Fortunately, C++ provides us with a mechanism to catch all types of exceptions. This is known as a catch-all handler. A catch-all handler works just like a normal catch block, except that instead of using a specific type to catch, it uses the ellipses operator (…) as the type to catch.

幸运的是,C++给我们提供了一个机制来捕捉所有类型的异常。这叫做catch-all处理器。一个catch-all处理器跟普通的catch块差不多,除了它不使用任何具体的类型来捕捉,它使用一个...作为类型来捕捉异常

If you recall from lesson 7.14 on ellipses and why to avoid them, ellipses were previously used to pass arguments of any type to a function. In this context, they represent exceptions of any data type. Here’s an simple example:

如果你回忆前面的内容,ellipses是用来把任何类型的参数传递给函数的,在这个情境中,它表示任何类型的异常。下面是一个例子:

#include <iostream>
 
int main()
{
	try
	{
		throw 5; // throw an int exception
	}
	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";
	}
}

Because there is no specific exception handler for type int, the catch-all handler catches this exception. This example produces the following result:

因为这里没有任何特定的处理int的异常处理器,所以catch-all就把这个异常给处理了。程序产生了下面的结果:

We caught an exception of an undetermined type

The catch-all handler should be placed last in the catch block chain. This is to ensure that exceptions can be caught by exception handlers tailored to specific data types if those handlers exist. Visual Studio enforces this constraint -- I am unsure if other compilers do. (Per reader Lonami in the comments below, GCC does too).

catch-all处理器应该放在catch块链的最后面。这个是来确保任何类型的异常都可以被捕获的手段。 Visual Studio 强制执行这样的约束,我不知道别的编译器是否也这样。

Often, the catch-all handler block is left empty:

通常catch-all处理器为空,忽视所有没有被预料到的异常。

catch(...) {} // ignore any unanticipated exceptions

This will catch any unanticipated exceptions and prevent them from stack unwinding to the top of your program, but does no specific error handling.

它会捕获所有没有想到的异常,阻止异常进一步传播,但实质上没有做任何异常处理工作。

Using the catch-all handler to wrap main()

One interesting use for the catch-all handler is to wrap the contents of main():

另一个有趣的用法是在main中捕获所有没有预料到的异常

#include <iostream>
 
int main()
{
 
    try
    {
        runGame();
    }
    catch(...)
    {
        std::cerr << "Abnormal termination\n";
    }
 
    saveState(); // Save user's game
    return 1;
}

In this case, if runGame() or any of the functions it calls throws an exception that is not caught, that exception will unwind up the stack and eventually get caught by this catch-all handler. This will prevent main() from terminating, and gives us a chance to print an error of our choosing and then save the user’s state before exiting. This can be useful to catch and handle problems that may be unanticipated.

在本例中,如果runGame或者任何它调用的函数抛出了异常,但是异常没有被处理,那么异常就会展开这个栈,向上传播,然后被main里面的这个catch-all处理掉。这会阻止main因为异常直接终止,而且给了我们打印错误的机会,保存用户的状态。这个东西用来捕获没有预料到的异常是很有用的。