2015-11-13 46 views
23

在哪些情况下,以下两个代码不相同?代码与try-catch-rethrow等效于代码w/o try-catch吗?

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

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

编辑只是为了澄清,我不感兴趣(i)中偏离上述模式(如在catch块更多的代码),也没有(II)旨在邀请约光顾的评论try - catch块的正确使用。

我正在寻找一个符合C++标准的合格答案。这个问题是由Cheers and hth. - Alfthis answer of mine的意见提示的,对于上面的代码是不等于,没有进一步的解释。


编辑他们确实是不同的。取决于是否在运行时发现异常处理程序(某些catch阻塞堆栈的较高层),将在后者中完成堆栈取消卷绕,但不一定取决于前者。

+0

*是否存在它们不相同的情况? – user2079303

+1

@ user2079303这正是我的问题。 – Walter

+0

哦,我明白了。我只看过问他们什么时候不是同等的问题,这意味着存在这种情况。我想知道@ Cheersandhth.-Alf指的是什么。 – user2079303

回答

17

后者的任务堆栈展开,而在以前这是实现定义。

相关标准引号(全部来自N3337):

[except.ctor]/1:作为从throw-表达到处理程序的控制通过时,析构函数被调用用于自输入try块构造的所有自动对象 。自动对象以与其构造完成相反的顺序销毁。

[except.ctor]/3:调用用于从try块到 掷表达的路径上构成的自动对象的析构函数的过程被称为“堆栈展开。” [...]

[except.terminate]/2: [当异常处理机制找不到抛出异常的处理程序],调用std::terminate()(18.8.3)。在没有找到匹配处理程序的情况下, 它是实现定义的,无论在调用std::terminate()之前堆栈是否展开。 [...]

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

+0

但是,编译器如何确定是否会找到一个异常处理程序,例如从一个调用函数中发现? – Walter

+2

@Walter我在使用clang的翻译单元中测试了这一点,'{/ * code *}'没有调用析构函数,但是重新推出了版本。我不太了解异常实现来说明它是如何完成的。 – TartanLlama

+1

@Walter:它不是编译器,而是运行时系统,它确定是否可以找到异常处理程序。由于在堆栈展开期间调用的析构函数不能改变将被激活的处理程序(如果有的话),系统是空闲的(我认为这是'[except.terminate]/2'的含义)在搜索处理程序时展开和破坏。但是,也可能首先完成搜索,并且只有在找到处理程序后才开始展开。在后一种情况下,在没有找到处理者的情况下不会发生任何破坏。 –

3

语义上相当。我不确定,如果有些编译器可能无法优化非传统try - catch。我宁愿离开try - catch块。这通常会使代码更易于取消访问。

+0

您是否可以支持您的声明(即代码是相同的)以标准格式的报价? – Walter

+0

@Walter请注意,我写道,它是“语义等价”。 AFAIK标准没有明确说明,但是从描述异常的语义和异常处理的情况可以明显看出。 – cdonat

1

万一你发现基本异常,它们是完全一样的。如果你在投掷之前做某些事情,比如记录,你只能从捕捉和重新抛出异常中受益。但你不应该抓到例外。只有在现在才能发现异常情况,如何恢复。

+1

没有发现“基本例外”。您可能正在考虑除C++以外的其他语言的功能。 – Peter

+0

@PalleDue否。如果他们只捕获基本异常*,它不会完全相同。问题中的代码捕获所有*抛出的对象。 – user2079303

+0

我回答的问题是C#。在编辑之前,没有提示它是C++。对于那个很抱歉。 –

-1

一些有意义的清理可以重投前catch块堆栈是解开来进行,如果资源没有RAII成语管理

 { 
      // 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; 
     } 
+0

这不回答这个问题。 – Walter

+0

@Walter我只是想指出一个情况,这两个代码构造可能有所不同 – NightFurry

+0

不,你没有,因为你改变了代码。 – Walter

3

假设“某些代码”没有表现出未定义的行为(在这种情况下,无论您是否添加try/catch块,所有投注都关闭),最终结果没有差异。它在技术上是由实现定义的(即实现必须记录它做了什么)如果从未捕获到异常,堆栈展开是否会发生,但是在这种情况下还没有任何实现不会展开堆栈的报告。如果发生堆栈展开,所有局部变量将超出作用域,并且具有析构函数的那些变量将调用析构函数。

在执行“某些代码”之前,可能会有也可能没有可测量的性能差异,与设置的开销相关联,捕获异常(如果有)和重新抛出以及任何其他清理。这种差异将依赖于编译器,并且与旧的编译器一样,具有潜在的重要意义。使用现代编译器,开销的差异(如果有的话)会少一些,因为异常和异常处理的实现技术已经有所改进。

+0

我不确定你是否回答我的问题。编译器无法确定这些代码是否位于try-catch块内,因此如何避免堆栈展开? – Walter

+1

@Walter编译器可以生成代码以* walk *(不展开)调用堆栈来查找异常处理程序,以及是否未找到相应的操作。 – jepio

+0

@jepio你的评论是最接近一个令人满意的答案(另见我最近编辑的OP)。 - 你能把它变成答案吗? – Walter

7

Elaboratring上Cheers and hth. - Alf's comment

http://en.cppreference.com/w/cpp/error/terminate:)

的std ::终止(由C++运行时调用异常处理 为以下任何原因未能:

1 )抛出异常并且未捕获(它是实现定义的 是否在此情况下完成任何堆栈展开

所以栈展开,如果您

{ 
    // 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。所以选择是否在运行时解开堆栈也是有意义的。

+0

啊,所以在异常没有被发现的情况下它是不同的。你还可以详细说明在程序何时会终止的情况下堆栈展开是否有用? – user2079303

+1

@ user2079303,显然要做任何清理代码。只是一个简单的例子:你有一个远程服务器,它只能接受一个连接。你的程序中会有一些'Connection'对象,并且它的析构函数会关闭连接,以便服务器准备好接受一个新的连接。 – Petr

+3

@ user2079303,或者甚至更简单:关闭文件输出流,以便缓冲的所有数据实际上都会碰到磁盘。 – Petr