异常处理中的混乱

时间:2009-09-27 06:01:30

标签: c++

考虑以下计划

#include <iostream>
#include<cstdlib>
using namespace std;

class E {
   public:
      const char* error;
      E(const char* arg) : error(arg) { }
};

void my_terminate() {
  cout << "Call to my_terminate" << endl;
}

struct A {
  A() { cout << "In constructor of A" << endl; }
  ~A(){
    cout << "In destructor of A" << endl;
    throw E("Exception thrown in ~A()");
  }
};

struct B {
  B() { cout << "In constructor of B" << endl; }
  ~B() { cout << "In destructor of B" << endl; }
};

int main() {

  set_terminate(my_terminate);

  try {
    cout << "In try block" << endl;
    A a;
    B b;
    throw E("Exception thrown in try block of main()"); // Line 36
  }
  catch (E& e) {
    cout << "Exception: " << e.error << endl;
  }
  catch (...) {
    cout << "Some exception caught in main()" << endl;
  }

  cout << "Resume execution of main()" << endl;

}

输出:

In try block
In constructor of A
In constructor of B
In destructor of B
In destructor of A
Call to my_terminate

Disallowed system call: SYS_kill

在第36行中,从main中的try块抛出异常。现在为什么这个异常没有被处理程序捕获?

相反,'堆栈展开'过程继续.A的析构函数也抛出一个异常,它再次没有被任何处理程序捕获,而是调用my_terminate,为什么?

为什么在这两种情况下不调用处理程序?

5 个答案:

答案 0 :(得分:17)

答案 1 :(得分:3)

当您从E中的try块中抛出原始main临时值时,运行时实现会构造类型为E的异常对象并搜索第一个catch 1}}可以处理异常的块。在这种情况下,这是紧随其后的catch (E& e)块。

当实现找到正确的catch来处理异常时,它会破坏所有必须超出范围的自动变量,方法是将throw发生的范围移出范围。渔获物居住。

在这种情况下,catch块本地的对象ab超出范围,因此必须销毁(按照创建它们的相反顺序)。但是,销毁a会导致抛出另一个异常。因为实现已经有一个未捕获的异常,并且已经为它试图达到的异常选择了一个catch处理程序,所以没有机制来处理这个新的异常。在这种情况下,规则是std::terminate,在您的情况下,您的终止处理程序将立即被调用。

您应该注意,my_terminate函数不符合terminate_handler,因为terminate_handler不能return并且必须终止程序执行(即不得throw 1}}或者)。你的内容是隐含的。

答案 2 :(得分:1)

<强> set_terminate

function将term_func安装为terminate调用的函数。 set_terminate与C ++异常处理一起使用,可以在抛出异常之前在程序中的任何位置调用。默认情况下终止调用中止。您可以通过编写自己的终止函数并使用函数名称作为参数调用set_terminate来更改此缺省值。 terminate调用最后一个函数作为set_terminate的参数。执行任何所需的清理任务后,

term_func应该退出程序

如果它没有退出(如果它返回其调用者),则调用abort

my_terminate()应如下所示:

void my_terminate() 
{
  cout << "Call to my_terminate" << endl;
  *
  *
  *
  exit(-1);

}

答案 3 :(得分:0)

在我的标准草案第15.2节中,它指出:

  

3调用析构函数的过程   用于构建的自动对象   从try块到a的路径   throw-expression被称为“堆栈   放松。“[注意:如果是析构函数   在堆栈展开期间调用退出   有一个例外,std :: terminate是   叫(15.5.1)。所以析构者应该   一般捕捉异常而不是让   它们从析构函数中传播出来。    - 后注]

他们已经广泛地定义了“堆栈展开”,它似乎涵盖了这种情况,即使它只是在一个函数中发生。我想有点清楚的是,实现期望析构函数不要尝试向外传播异常。

答案 4 :(得分:0)

这是问题所在。你的A的析构函数抛出,这是一个坏的东西。重新抛出异常,或者在异常处理程序中抛出一个新异常是犹太的,因为堆栈展开是表现良好的。在这种情况下,当前堆栈帧只有一个异常处于活动状态。当析构函数在堆栈展开过程中抛出异常时,两个异常在相同的堆栈帧中存活,即它们在同一级别上展开堆栈。在你的情况下,这是两个E对象。那么一个人选择遵循哪一个?你不能同时遵循这两个标准,因此标准规定将调用 terminate() 。您可以通过从标题 std::set_terminate() 中传递一个 <exception> 来使异常系统使用您的自定义终止例程。

您似乎认为 std::terminate() 处理程序可以通过返回来恢复您的程序,但这是未定义的行为。
如果你绝对需要从析构函数中抛出并且不能在析构函数本身中包含异常处理程序,那么这是一个解决方法:

标题 uncaught_exception() 中的函数 <exception> 会在抛出异常但尚未捕获的情况下返回true。如果它返回true,则表示进程处于堆栈展开的中间,展开堆栈并调用析构函数,直到找到正确的异常处理程序。使用抛出的析构函数内部的函数,以便在没有发生堆栈展开时仅抛出

以下是如何使用 uncaught_exception() 的示例(尽管,这是一个非常糟糕的主意):

#include <iostream>
#include <exception>
#include <stdexcept>
#include <sstream>
#include <cstdlib>

void termhandler()
{
    std::cout << "Inside terminate()" << std::endl;
    abort();
}

class Foo
{
public:
    Foo(int val) : i(val){ std::cout << "Created Foo object " << i << std::endl; }
    ~Foo()
    {
        if(std::uncaught_exception()){
            std::cout << "~Foo::Foo() object " << i << " : " << "Stack unwinding in progress. Can't throw!" << std::endl;
        } else {
            std::cout << "~Foo::Foo() object " << i << " : " << "Throwing test exception." << std::endl;
            std::ostringstream strm;
            strm << i;
            std::runtime_error e("Exception from ~Foo::Foo() object " + strm.str());
            throw e;
        }
    }
    int i;
};

int main()
{
    try {
        std::set_terminate(termhandler);    
        Foo A(1);
        Foo B(2);
    } catch(std::exception& e){
        std::cout << "Caught exception in main() : " << e.what() << std::endl;
    }
}

其中给出了以下输出:

创建Foo对象1
创建了Foo对象2
~Foo :: Foo()对象2:抛出测试异常。
~Foo :: Foo()对象1:正在进行堆栈展开。不能扔!
main()中的捕获异常:来自~Foo :: Foo()对象的异常2