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的一致性更加清晰和安全。
答案 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) - 删除对堆栈展开的要求允许更积极的优化