2015-11-13 49 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