2012-02-13 31 views
4

此問題是"Constructor with by-value parameter & noexcept"的對偶。這個問題表明,值函數參數的生命週期管理由調用函數處理;因此,調用者處理髮生的任何異常,被調用函數可以標記爲noexcept。我想知道如何處理輸出結果noexcept按值返回功能並且不接受

MyType MyFunction(SomeType const &x) noexcept; 

//... 

void MyCaller() 
{ 
    MyType test1 = MyFunction(RandomSomeType()); 
    MyType test2{ MyFunction(RandomSomeType()) }; 
    //... 
    test1 = MyFunction(RandomSomeType()); 
    test2 = std::move(MyFunction(RandomSomeType())); 
    MyFunction(RandomSomeType()); // return value goes to oblivion 
} 

假設在MyFunction內成功創建了返回值。假設MyType的適當特殊成員函數(複製/移動分配/構造)可能不是noexcept

  1. 執行RVO/NRVO /無論從 - C++關於返回值從調用的函數給調用者轉移11條規則意味着轉移總是成功的沒有拋出不管的的noexcept狀態適當的特殊成員功能?
  2. 如果對上一個問題的答案是「否」,那麼如果返回值傳輸拋出,則針對被調用的函數或調用者計數異常嗎?
  3. 如果上一個問題的答案是「被調用函數」,那麼MyFunction上的普通noexcept標記將導致調用std::terminate。應該將MyFunctionnoexcept配置文件更改爲?當我在Usenet上詢問這個問題時,被訪者認爲它應該是std::is_nothrow_move_assignable<MyType>::value。 (請注意,MyCaller使用了幾種使用返回值的方法,但MyFunction將不知道哪一個正在使用!答案必須涵蓋所有情況。)如果MyType更改爲可複製但不可移動?

因此,如果第二和第三個問題最糟糕的情況是準確的,那麼按值返回任何函數不能有一個簡單的noexcept如果返回類型有一個界外球能夠感動!現在使用可拋出移動的類型應該很少見,但是每次使用按值返回時,模板代碼仍然必須使用is_nothrow_move_assignable「自己弄髒」。

我覺得做被叫功能負責被打破:

MyType MyFunction(SomeType const &x) noexcept(???) 
{ 
    //... 
    try { 
     return SOME_EXPRESSION; 

     // What happens if the creation of SOME_EXPRESSION succeeds, but the 
     // move-assignment (or whatever) transferring the result fails? Is 
     // this try/catch triggered? Or is there no place lexically this 
     // function can block a throwing move!? 
    } catch (...) { 
     return MyType(); 

     // Note that even if default-construction doesn't throw, the 
     // move-assignment may throw (again)! Now what? 
    } 
} 

這個問題,至少對我來說,似乎是在調用者的結束可以解決的(只是包裝與布展分配的try/catch),但從被調用函數的末尾不可修復。我認爲調用者必須處理這個問題,即使我們需要改變C++規則來做到這一點。或者至少需要某種缺陷報告。

+0

糾正我,如果我錯了,但RVO/NRVO /移動優化不影響誰構建對象,只有它構建的內存。你的對象將直接在調用者(通常是調用者的棧)提供的內存位置構造,但構造函數調用本身將位於被調用者的棧幀內。所以,如果構造函數拋出,它將被調用者「拋出」它。 – lapk 2012-02-14 06:22:32

+0

對不起,我的意思是「在被調用者的代碼段內」,而不是「在被調用者的棧幀內」。 – lapk 2012-02-14 07:31:50

回答

3

要回答你的問題的一部分,你可以要求某種類型是否是無拋出constructible:

#include <type_traits> 

MyType MyFunction(SomeType const &x) 
    noexcept(std::is_nothrow_move_constructible<MyType>::value) 
{ 
    // .... 
} 
+1

但是C++ 11的返回值處理在概念上總是使用移動語義嗎?複製語義僅在類型不可移動時使用,對嗎?此外,不要''__ move_ *'類使用/調用不可移動類型的'* copy *'版本;換句話說,您不需要明確考慮不可移動性,並且只需要'* move *'測試? – CTMacUser 2012-02-14 05:56:54

+0

@CTMacUser:你說得對,這是無用的檢查。 – Xeo 2012-02-14 06:04:47

+0

另一方面,轉移總是被認爲是移動建設,而不是移動分配(正如我上次有人告訴我的那樣)。我希望答案不是「它取決於」,因爲'MyFunction'的程序員不知道結果是在構造函數/函數參數中,在賦值的右邊,還是在s /他正在寫它。 – CTMacUser 2012-02-15 04:27:18

0

我覺得你的問題是混亂的,因爲你在談論一個來自被叫「轉移」到調用者,這不是我們在C++中使用的術語。考慮函數返回值的最簡單方法是被調用者通過「返回槽」(由被調用者構造並被調用者銷燬的臨時對象)與調用者進行通信。被叫方負責將返回值構建到「返回時隙」中,並且主叫方負責從「返回時隙」中獲取該值(如果需要),然後銷燬「返回時隙」中的任何剩餘部分。

MyType MyFunction(SomeType const &x) noexcept 
{ 
    return SOME_EXPRESSION; 
} 

void MyCaller() 
{ 
    MyType test1 = MyFunction(RandomSomeType()); // A 
    MyType test2{ MyFunction(RandomSomeType()) }; // B 
    //... 
    test1 = MyFunction(RandomSomeType()); // C 
    test2 = std::move(MyFunction(RandomSomeType())); // D 
    MyFunction(RandomSomeType()); // E 
} 

第一:return SOME_EXPRESSION;引起SOME_EXPRESSION結果的聲明得到搬進的MyFunction「返回槽」。此舉可能是elided。如果移動不被消除,那麼MyType的移動構造函數將被調用。如果移動構造函數拋出異常,則可以通過圍繞return本身的嘗試塊或通過function try block捕獲異常。

案例A:在MyFunction(可能會被刪減)內部有移動對象,然後移動到test1(可能會被刪除)。

案例B:與案例A相同。

案例C:有MyFunction(可能會被忽略)中的move-ctor,然後移動分配到test1

案例D:與案例C相同。致電std::move並沒有提供任何好處,編寫它的風格很糟糕。

案例E:在MyFunction(可能會被忽略)中有move-ctor,就是這樣。

如果在移動或移動到test1過程中拋出異常,可以通過在處理塊中包裝處理test1的代碼來捕獲這些異常。 MyFunction內的代碼在這一點上完全不相關; MyFunction不知道或關心調用者將如何處理返回的對象。只有調用者知道,並且只有調用者能夠捕獲調用者產生的異常。