为什么使用throw "nothing"会导致程序终止?

15
const int MIN_NUMBER = 4;
class Temp
{
public:

    Temp(int x) : X(x)
    {
    }

    bool getX() const
    {
        try
        {
            if( X < MIN_NUMBER)
            {
                //By mistake throwing any specific exception was missed out
                //Program terminated here
                throw ;
            }
        }
        catch (bool bTemp)
        {
            cout<<"catch(bool) exception";

        }
        catch(...)
        {
            cout<<"catch... exception";
        }
        return X;
    }

private:
    int X;
};



int main(int argc, char* argv[])
{
    Temp *pTemp = NULL;
    try
    {
        pTemp = new Temp(3);
        int nX = pTemp->getX();
        delete pTemp;
    }
    catch(...)
    {
        cout<<"cought exception";
    }

    cout<<"success";
    return 0;
}

在上述代码中,getX()方法原本意图抛出false,但由于人为疏忽,漏写了false。这看似无害的代码却让应用程序崩溃。

我的问题是,当我们抛出“什么都没有”的时候,程序为什么会终止?

我有一点理解,throw;基本上是"重新抛出",必须在异常处理程序(catch)中使用。在任何其他地方使用这个概念将导致程序终止,那么为什么编译器在编译期间不发出警告呢?

7个回答

29
这是预期行为。来自 C++ 标准:

如果当前没有正在处理异常,执行没有操作数的 throw 表达式将调用 terminate()(15.5.1)。

至于编译器为什么无法诊断此问题,这需要进行相当复杂的流分析,而且我认为编译器编写者不会认为这是一项划算的成本。C++(以及其他语言)充满了可能在理论上可以被编译器捕获但实际上并未被捕获的错误。

这并没有回答aJ的另一个问题,即为什么首先使用'throw;'有用。 - Aaron
我想知道为什么编译器不能检测到这个问题? - aJ.
我猜编译器没有检测到它,因为它是C++规范的一部分。是的,这很糟糕 - 不,你不应该这样做。这就是C++ - 不管你喜欢还是讨厌它 :) - cwap
1
这是由于涉及运行时行为,编译器无法检测到。 - anon
假设我更改getX()以删除try-catch块: bool getX() const { if( X < MIN_NUMBER) { throw ; } return X; } - aJ.
2
aJ,这没关系。人们可以在catch块内调用函数,然后抛出;是有效的。 - Johannes Schaub - litb

12
为了详细解释Neil的答案:throw;将尝试重新引发当前正在解除的异常 - 如果有多个异常正在被解除,它会尝试重新引发最近的一个。如果没有解除任何异常,则调用terminate()以表示程序执行了一些不良操作。
至于你的下一个问题,为什么编译器不会在catch块外对throw;发出警告,因为编译器无法在编译时确定throw;行是否可能在catch块的上下文中执行。考虑以下情况:
// you can try executing this code on [http://codepad.org/pZv9VgiX][1]
#include <iostream>
using namespace std;

void f() {
    throw 1;
}
void g() {
    // will look at int and char exceptions
    try { 
        throw;
    } catch (int xyz){
        cout << "caught int " << xyz << "\n";
    } catch (char xyz){
        cout << "caught char " << xyz << "\n";
    }
}
void h() {
    try {
        f();
    } catch (...) {
        // use g as a common exception filter
        g();
    }
}
int main(){
    try {
        h();
    } catch (...) {
        cout << "some other exception.\n";
    }
}
在这个程序中,g() 起到异常过滤器的作用,并可从 h() 和任何其他需要使用此异常处理行为的函数中使用。你甚至可以想象更复杂的情况:
void attempt_recovery() {
    try{
        // do stuff
        return;

    } catch (...) {}

    // throw original exception cause
    throw;
}
void do_something() {
    for(;;) {
        try {
            // do stuff
        } catch (...) {
            attempt_recovery();
        }
    }
}

如果在 do_something 中发生异常,将会调用恢复代码。如果恢复代码成功,原始异常将被忘记并重新尝试任务。如果恢复代码失败,则忽略该失败并重新抛出以前的异常。这是因为 attempt_recovery 中的 throw; 在 do_something 的 catch 块的上下文中被调用。


10

来自C++标准:

15.1 抛出异常

...

如果没有正在处理的异常,执行不带操作数的 throw-exception 调用 terminate()

编译器无法可靠地捕获此类错误的原因在于,异常处理程序可以调用函数/方法,因此编译器无法知道 throw 是否发生在 catch 中。这基本上是一个运行时的问题。


0

用一个例子来补充之前的答案,说明编译器不能检测到问题的情况和原因:

// Centralized exception processing (if it makes sense)
void processException()
{
   try {
      throw;
   }
   catch ( std::exception const & e )
   {
      std::cout << "Caught std::exception: " << e.what() << std::endl;
   }
   catch ( ... )
   {
      std::cout << "Caught unknown exception" << std::endl;
   }
}

int main()
{
   try
   {
      throw 1;
   }
   catch (...)
   {
      processException(); // correct, still in the catch clause
   }
   processException(); // terminate() no alive exception at the time of throw.
}

编译函数processException时,编译器无法知道它将如何被调用和何时被调用。

0
我对throw的理解很少;它基本上是“重新抛出”,必须在异常处理程序(catch)中使用。在任何其他地方使用此概念都会导致程序终止,那么为什么编译器在编译期间不发出警告呢?
重新抛出很有用。假设您有一个调用堆栈,每个级别都添加一些上下文资源对象以进行最终调用。现在,当您在叶级别遇到异常时,您将期望对对象创建的任何资源进行一些清理操作。但这还不是全部,上面的调用者可能还分配了一些需要释放的资源。你怎么做?你重新抛出
然而,你所拥有的并不是重新抛出。这是一种放弃的信号,在尝试捕获和处理引发的所有异常失败后。

0
在没有参数的 catch 块中抛出异常将重新抛出被捕获的相同异常,因此它将在更高级别上被捕获。
在没有参数的 catch 块外抛出异常将导致程序终止。

-1

你没有任何需要捕获的东西,所以异常一直往上冒泡。即使是catch(...)也需要一些东西


我认为他在问为什么在catch块之外使用"throw;"是合法的,而不是为什么它没有被捕获。 - jalf
@jalf:不,我认为他在问为什么它没有被捕获。这在Neil Butterworth的回答中有所解释。 - chaos
@aJ:编译器此时无法知道。您可以创建一个函数,重新抛出最后捕获的异常并处理它(我们这样做是为了集中一些CORBA异常)。在编译该函数时,编译器不会知道调用者是否实际上已经捕获了异常。 - David Rodríguez - dribeas
他提出了两个问题 - 为什么它没有被捕获?以及为什么编译器没有抱怨重新抛出不在catch内? - Michael Burr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接