throw() 函数应该总是在异常时展开堆栈并允许捕获异常还是必须调用 std::terminate ?



我感兴趣的是这是否是标准强制的,以及某些编译器是否违反了它。我的观察是:

  • Visual Studio 2015/2017(使用/EHsc):throw() 函数中的堆栈不会展开(不调用 d-tor),但异常会退出函数并被 try/catch 捕获。没有调用 std::终止。
  • gcc (6.3.0),throw() 函数中的堆栈被解开,但随后调用 std::terminate (try/catch 不能咳嗽异常)。但是在 7.0(当前 HEAD)中,没有堆栈被展开,std::termiante 立即被调用。 实际上 gcc 7.0 甚至警告了这一点:warning: throw will always call terminate() [-Wterminate]NoExceptFunctionWithObj2().它使 throw() 表现为 noexcept(true)。

  • clang,在所有版本中,我都检查了展开函数堆栈(调用对象 d-tors),然后调用 std::terminate 。

测试代码:

#include <iostream>
#include <string>
#include <vector>
struct TestDataWithoutNoexcept {
TestDataWithoutNoexcept() {
std::cout << __FUNCTION__ << "n";
}
~TestDataWithoutNoexcept() {
std::cout << __FUNCTION__ << "n";
}
TestDataWithoutNoexcept(TestDataWithoutNoexcept const & rhs) {
std::cout << __FUNCTION__ << "n";
}
TestDataWithoutNoexcept(TestDataWithoutNoexcept && rhs) {
std::cout << __FUNCTION__ << "n";
}
TestDataWithoutNoexcept& operator=(TestDataWithoutNoexcept const& rhs) {
std::cout << __FUNCTION__ << "n";
}
};
void NoExceptFunctionWithObj1() noexcept {
TestDataWithoutNoexcept test;
throw std::runtime_error("NoExceptFunctionWithObj1 ex.");
}
void NoExceptFunctionWithObj2() throw() {
TestDataWithoutNoexcept test;
throw std::runtime_error("NoExceptFunctionWithObj2 ex.");
}
int main()
{
// Now lets see whether stack is being unwound when exception is thrown in noexcept versus throw() function.
std::cout << "n See how dtors are called in noexcept or throw() functionsn";
try {
//NoExceptFunctionWithObj1();
}
catch (std::runtime_error& ex) {
std::cout << ex.what();
}
try {
NoExceptFunctionWithObj2();
}
catch (std::runtime_error& ex) {
std::cout << "nShouldn't this be shown? : " << ex.what();
}
}

是的,应该调用std::terminate。最新公布的标准草案说:

15.4 [规格除外],第12段

一 异常规范 是 非投掷 如果是形式 投掷() , 否,除了 或 不,除了( 常量表达式 ) 其中 不断- 表达 收益 率 真 .

这意味着throw()严格等同于noexcept(true)throw()C++17中已弃用。

15.5.1 [终止除外]

当搜索处理程序 (15.3) 遇到函数的最外层块时,具有 不,除了—— 规范 不允许异常 (15.4) [...] std::terminate() 称为 (18.8.3)。在找不到匹配处理程序的情况下, 堆栈是否在之前解开都是实现定义的 std::terminate() 被称为

不调用std::terminate意味着 MSVC 不合规。

关于堆栈的处理,编译器在您的示例中执行它想要的解开或不展开它 - 这被指定为实现定义的

从历史上看(在 C++11 之前),在这种情况下必须进行堆栈平仓。然而,事实证明,这种强制行为的运行时成本太高,并且它抑制了编译器进行一些优化(即使在非异常情况下)。因此,编译器现在可以自由地将其排除在外。

经@mike澄清后编辑。

最新更新