C++ 异常处理中的混乱

C++ 异常处理中的混乱,c++,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; }

考虑以下程序

#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;

}

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

相反,“堆栈展开”过程将继续。A的析构函数也会抛出一个异常,该异常也不会被任何处理程序捕获,而是调用了
my\u terminate
,为什么

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


您在
的析构函数中抛出异常,这是您不应该做的。

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

3调用析构函数的过程 用于在上构造的自动对象 从try块到try块的路径 抛出表达式称为“堆栈” [注:如果是析构函数 在堆栈展开退出期间调用 除了一个例外,std::terminate是 调用(15.5.1)。所以析构函数应该 通常捕获异常,而不让 它们从析构函数中传播出去。 -[完注]


他们对“堆栈展开”的定义足够宽泛,似乎涵盖了这种情况,即使它都发生在一个函数中。我想很明显,实现期望析构函数不会尝试向外传播异常。

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

当实现找到正确的
catch
来处理异常时,它会通过将抛出的
范围移出catch所在的范围,从而销毁所有必须超出范围的自动变量

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


您应该注意,您的
my_terminate
函数不是一致的
terminate_处理程序
,因为
terminate_处理程序
不能
返回
,必须终止程序执行(即不能
抛出
)。您的将隐式返回。

set\u terminate

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

term_func应退出程序

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

我的_terminate()应该如下所示:

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

}
void my_terminate()
{

cout这就是问题所在。A的析构函数抛出异常,这是一件坏事。重新抛出异常或在异常处理程序中抛出新异常是合乎礼仪的,因为堆栈展开行为良好。在这种情况下,当前堆栈帧中只有一个异常处于活动状态。当析构函数抛出异常时但是,在堆栈展开过程中,两个异常在同一堆栈帧中处于活动状态,即它们在同一级别上展开堆栈。在您的情况下,这是两个e对象。那么其中一个选择遵循哪一个?您不能同时遵循这两个,因此标准规定
终止()将调用
。您可以通过从标题
传递一个带有
std::set_terminate()
的异常系统来使用自定义终止例程

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

函数
未捕获的异常()< /代码> <强> >从一个异常已被抛出但尚未捕获的情况下返回true。如果它返回true,则意味着进程处于堆栈展开的中间,展开堆栈并调用析构函数,直到找到适当的异常处理程序。ow,这样它们只在堆栈未展开时抛出

下面是一个关于如何使用
uncaught\u exception()
的示例(尽管这是一个非常糟糕的主意):

#包括
#包括
#包括
#包括
#包括
void termhandler()
{

std::此处不能包含源代码。指向外部源代码的链接可能会随着时间的推移而中断。我无法格式化我在此处发布的代码。因此我提供了一个外部链接。请参阅此处:在第36行中,从main中的try块引发了一个异常。现在,为什么处理程序未捕获该异常?“堆栈展开过程应在此之后继续”。如果我错了,请纠正我。从我链接的常见问题解答中可以看出:“在堆栈展开过程中,所有堆栈帧中的所有本地对象都会被析构函数破坏。如果其中一个析构函数抛出异常(比如它抛出一个条形对象)
void my_terminate() 
{
  cout << "Call to my_terminate" << endl;
  *
  *
  *
  exit(-1);

}
#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;
    }
}