带有try-catch-rethrow的代码是否等同于代码w/o try-catch?

Is code with try-catch-rethrow equivalent to code w/o try-catch?

下面两种代码在什么情况下不等价?

{
  // some code, may throw and/or have side effects
}

try {
  // same code as above
} catch(...) {
  throw;
}

edit 澄清一下,我对 (i) 与上述模式的偏差(例如 catch 块中的更多代码)也不感兴趣 (ii) 打算邀请关于正确使用 try-catch 块的光顾评论。

我正在寻找参考 C++ 标准的合格答案。这个问题是由 Cheers and hth. - Alf to 的评论提示的,指出 w/o 进一步解释上述代码 不等价


edit 它们确实不同。堆栈展开将在后者中完成,但不一定在前者中完成,这取决于是否在 运行 时间找到异常处理程序(一些 catch 块在堆栈的更高层)。

语义上是等价的。我不确定,如果某些编译器可能无法优化不必要的 try-catch 。我宁愿离开 try-catch 块。这通常会使代码更容易理解。

如果您捕捉到基本异常,它们是完全相同的。如果你在抛出之前做一些事情,比如日志记录,你只会从捕获和重新抛出异常中受益。但是你不应该捕获异常。 Only ever catch exceptions 你现在如何从中恢复。

如果资源未按 RAII 习惯用法进行管理,则可以在重新抛出之前在 catch 块中执行一些有意义的清理

       {
          // some code, may throw and/or have side effects
        }

        try {
          // same code as above
        } catch(...) {
//Some meaningful clean up can be performed here if resources not managed as RAII idiom
          throw;
        }

后者要求堆栈展开,而在前者中,如果堆栈展开,则由实现定义。

相关标准引用(均来自N3337):

[except.ctor]/1: As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

[except.ctor]/3: The process of calling destructors for automatic objects constructed on the path from a try block to a throw-expression is called “stack unwinding.” [...]

[except.terminate]/2: [When the exception handling mechanism cannot find a handler for a throw exception], std::terminate() is called (18.8.3). In the situation where no matching handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate() is called. [...]

因此,如果您想保证您的自动对象在未处理的异常情况下有它们的析构函数 运行(例如,某些持久存储必须在销毁时发生突变),那么 try {/*code*/} catch (...) {throw;} 将这样做,但 {/*code*/} 不会。

假设 "some code" 没有表现出未定义的行为(在这种情况下,所有的赌注都会被取消,无论您是否添加 try/catch 块),最终结果将没有差异.如果从未捕获到异常,是否会发生堆栈展开在技术上由实现定义(即一个实现必须记录它所做的事情),但尚未有任何在这种情况下不展开堆栈的实现的报告。如果发生堆栈展开,所有局部变量都将超出作用域,具有析构函数的局部变量将调用析构函数。

可能存在也可能不存在可衡量的性能差异,这与执行 "some code" 之前的设置开销相关,捕获异常(如果有)并重新抛出,以及任何额外的清理。差异将取决于编译器,并且对于旧的编译器,可能很重要。对于现代编译器,开销的差异(如果有的话)会稍微小一些,因为异常和异常处理的实现技术已经改进。

详述

来自 http://en.cppreference.com/w/cpp/error/terminate :

std::terminate() is called by the C++ runtime when exception handling fails for any of the following reasons:

1) an exception is thrown and not caught (it is implementation-defined whether any stack unwinding is done in this case)

所以如果你的

堆栈展开可能不会发生
{
  // some code, may throw and/or have side effects
}

不在另一个 try/catch 区块内。

示例:

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

int main()
{
//    try {
        A a;
        throw 1;
//    } catch(...) {
//        throw;
//    }
}

Under coliru's gcc 5.2.0 with -O2 不打印 ~A(),而 try/catch 打印。

UPD:关于你对单独编译单元的编辑,刚刚用我本地的 gcc 4.8.2 测试过,行为是一样的:如果没有 catch。具体例子:

a.h:

struct A {
   A();
   ~A();
};

void foo();

a.cpp:

#include <iostream>
using namespace std;

struct A {
   A() {}
   ~A() { cout << "~A()" << endl; }
};

void foo() {
    A a;
    throw 1;
}

main.cpp:

#include "a.h"

int main () {
   //try {
    foo();
   //} catch(...) {
   //  throw;
   //}
}

我认为是否有catch是在运行时判断的,因为反正在运行时抛出异常,程序需要寻找catch.所以选择是否在运行时间展开堆栈也是有意义的。