2016-08-02 112 views
8

我有一個C++類,在構造函數失敗時拋出一個異常。如何分配本類的本地實例(不使用new)並處理任何可能的異常,同時保持try塊範圍儘可能小?如何捕獲構造函數異常?

從本質上講,我找了C++相當於下面的Java成語:

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

我不想從x.doSomething()捕獲異常,僅構造

我想我正在尋找的是一種方法來分開聲明和x的初始化。

是否有可能在不使用堆分配和指針的情況下完成此操作?

+0

是的。將所有內容放在try塊內的成功路徑上。你會留在功能範圍內。 – StoryTeller

+0

@StoryTeller如果我不想從例如'x.doSomething()',只有構造函數的異常? –

+0

@AndrewSun使用不同的異常,或將'x.doSomething()'放在內部異常塊中。 – Holt

回答

11

可以使用std::optional從C++ 17:

bool foo() { 
    std::optional<Bar> x; // x contains nothing; no Bar constructed 
    try { 
     x.emplace();  // construct Bar (via default constructor) 
    } catch (const Exception& e) { 
     return false; 
    } 
    x->doSomething();  // call Bar::doSomething() via x (also (*x).doSomething()) 
    return true; 
} 
+0

@ Jarod42我對於如何將它寫入'Bar'的Defualt ctor猶豫不決。 'x.emplace()'? 'x.emplace({})'? – songyuanyao

+1

默認構造函數是'x.emplace()'。後者不會編譯。 – Jarod42

+0

@ Jarod42謝謝! – songyuanyao

2

你的

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = std::make_unique<Bar>(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    return true; 
} 
2

變種之間進行選擇一般來說,如果你想避免堆分配,你不能從它的定義局部變量的聲明分開。所以,如果你是一切都在一個單一的功能結合起來,你就必須做環繞的x整個範圍與try/catch塊:

boolean foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 
5

是的,它是可能的,如果你把所有的代碼try子句中,爲例如,通過使用function try block(以避免不必要的嵌套和範圍):

bool foo() try 
{ 
    Bar x; 
    x.doSomething(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

或在try子句調用另一個函數,它不會真正的工作:

void real_foo() 
{ 
    Bar x; 
    x.doSomething(); 
} 

bool foo() try 
{ 
    real_foo(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

請注意,在構造函數中拋出異常通常不是一個好主意,因爲這會停止構造對象,並且不會調用其析構函數。


正如霍爾特指出,這也將趕上從doSomething通話例外。有解決了兩種方式:

  1. 簡單而標準的方法:使用指針

  2. 使用兩階段構造:有一個默認的構造函數不能拋出異常,然後調用一個特殊的「構造」函數來引發異常。

第二種方式是共同的前C++是標準化的,併爲Symbian system在代碼廣泛使用。這種情況不常見,因爲使用指針更容易,更簡單,特別是在今天有很好的智能指針可用的情況下。我真的不推薦現代C++中的第二種方法。

最簡單的方法當然是確保構造函數不能拋出異常,或者拋出任何異常,那麼它們就是程序無法繼續並且終止程序的本質。正如對你的問題的評論所指出的那樣,C++中的異常是很昂貴的,然後我們也有被遺棄的構造問題,並且所有在C++中使用異常都只能在特殊情況下完成。 C++不是Java,即使在這兩種語言中都有類似的結構,你也不應該這樣對待它。

如果您仍想從構造函數中拋出異常,實際上只有第三種方法來捕獲這些異常:使用頂部的代碼示例之一,並只拋出doSomething永遠不會拋出的特定異常,然後捕獲這些特定只有構造函數。

+0

函數嘗試塊是不需要的。定期'嘗試抓住'就夠了。 – Jarod42

+0

@ Jarod42,那麼'Bar'必須在另一個塊中,而不是在功能級別塊中。 – Ajay

+1

這不會模仿給定java代碼的行爲,因爲您將從'x.doSomething()'中捕獲異常。 – Holt

2

號從Java例子,你將有這兩個可能性之間進行選擇:

沒有指針:

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 

隨着指針:

bool foo() { 
    Bar* x = nullptr; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 

    delete x; // don't forget to free memory 

    return true; 
} 

或者使用管理指針:

#include <memory> 

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = new Bar();    // until C++14 
     x = std::make_unique<Bar>(); // since C++14 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 
+3

你可能想在某處添加一個'delete x'; – CompuChip

+0

通常,我使用託管指針。我的壞^^ – wasthishelpful

+1

是的。現在第二個例子的缺點是,你真的想要捕捉'x-> doSomething'中的任何異常,以確保'delete'被執行,但是你需要一個單獨的try-catch塊來重新拋出 - unique_ptr能很好地解決潛在的問題。很好,你添加了它。 – CompuChip

5

這個Java成語不會很好地轉化爲C++,因爲Bar x;將需要默認構造函數即使您的真實構造函數需要傳遞參數。

我建議戰鬥語言這種程度 - 擴大try塊就足夠了 - 但是如果你真的縮小,那麼你可以使用的功能,依靠返回值優化消除於未然值副本:

Bar foobar() 
{ 
    try { 
     return Bar(); 
    } catch (Exception& e){ 
     /* Do something here like throwing a specific construction exception 
      which you intercept at the call site.*/ 
    } 
} 

但是,真的,你可以拋出一個特定的異常構造,所以完全避免這個函數的方法。

+1

RVO和移動語義使我認爲這是最好的解決方案。 – StoryTeller

+0

在C++ 17之前,OP可以處理複製/移動構造函數異常。 – Jarod42

+0

@ Jarod42,C++ 17改變了什麼? (只是爲了我自己的好奇而問)。 – StoryTeller

1

在修訂討論的OP補充說,

我不想從x.doSomething()捕獲異常的要求,只有[局部變量]的構造函數。

一個簡單的方法來翻譯的Java代碼

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

&hellip;到C++,然後使用一個Optional_類(如巴頓Nackmann Fallibleboost::optional或C++ 17 std::optional

auto foo() 
    -> bool 
{ 
    Optional_<Bar> xo; 
    try 
    { 
     xo.emplace(); 
    } 
    catch(...) 
    { 
     return false; 
    } 

    Bar& x = *xo; 
    // Possibly other code here, then: 
    x.doSomething(); 
    return true; 
} 

一個很好的替代方案是重構該代碼,如下所示:

struct Something_failure {}; 

void do_something(Bar& o) 
{ 
    // Possibly other code here, then: 
    o.doSomething(); 
} 

auto foo() 
    -> bool 
{ 
    try 
    { 
     Bar x; 

     do_something(x); 
     return true; 
    } 
    catch(Something_failure const&) 
    { 
     throw; 
    } 
    catch(...) 
    {} 
    return false; 
} 

如果你不喜歡上述方法,那麼你總是可以去動態分配的Bar實例,例如使用std::unique_ptr進行有保證的清理,但是這具有動態分配的一般開銷。在Java中,大多數每個對象都是動態分配的,所以看起來似乎不是一個嚴重的缺點。但是在C++中,大多數對象是超快堆棧分配的,所以與普通操作相比,動態分配是一個非常慢的操作,所以必須對動態分配的概念簡單性進行加權。