为什么只有处理异常才能保证堆栈展开?

时间:2017-07-03 13:51:43

标签: c++ exception

C ++ standard说([except.handle]/9):

  

如果找不到匹配的处理程序,则调用函数std :: terminate();在调用std :: terminate()之前是否展开堆栈是实现定义的

例如,下面的代码行为(是否会打印S::~S())是实现定义的:

struct S {
    S() { std::cout << "S::S()" << std::endl; }
    ~S() { std::cout << "S::~S()" << std::endl; }
};

int main() {
    S s;
    throw std::runtime_error("exception");
}

我想知道深入:为什么要定义实现?如果未捕获异常(类似于顶级函数中的std::terminate()),为什么在调用try{ ... } catch(...) { throw; }之前无法取消上下文的上下文?一目了然,这种行为与RAII的一致性更加清晰和安全。

2 个答案:

答案 0 :(得分:11)

如果未捕获到异常,则会调用std::terminate。我们失败了,主机环境需要介入并且(可能)在我们之后进行清理。在这种情况下解开堆栈就像给一个神风飞行员一个头盔。

因此,对于托管环境,只做任何事情并让主机清理可能更有意义。

现在,如果你是一个独立的实现,并且抛出异常,那么在你之后没有人可以清理。在这种情况下,一个实现应该预先形成堆栈展开,因为那应该是清理混乱的东西。

该标准将其留给实现以促进这两种截然不同的执行环境。

就像@Matteo指出的那样,std::terminate在任何潜在的展开之前被调用,因为你可以为它设置一个处理程序。并且该处理程序可以对堆栈状态执行一些有用的操作,只要堆栈尚未解开。

答案 1 :(得分:2)

这本身并不是最强大的原因,但是将其留给实现可以实现更多优化。 E.g:

class Foo { /*...*/ };

void f();

int main()
{
    for ( int i = 0; i < 1000000; ++i )
    {
        Foo myFoo;
        f();
    }
}

这里,如果myFoo抛出,实现可以选择不销毁f(),这可能会减少代码大小和/或提高性能。理由是如果你不编写异常处理程序,你不会指望f()抛出,并且可以采用快捷方式。这可能听起来有点弱,但这类似于noexcept(C ++ 11)与throw()子句(C ++ 98) - 删除对堆栈展开的要求允许更积极的优化