2017-02-26 37 views
2

我希望我的函數返回成功的指示或描述失敗性質的對象。我通常會爲此使用異常,但我被告知不要將它們用於通用代碼路徑,並且由於各種原因,這組函數可能會經常失敗。如何從函數中返回成功或錯誤對象?

我的想法是使用C++ 17的std::optional,因爲我不必在不需要時返回完整的錯誤對象。因此,對於可選的,如果函數不成功,則返回錯誤對象,否則可選項爲空。與此相關的問題是它逆轉了對返回值的期望(即true通常表示成功而非失敗)。

我可以讓人們使用is_success功能,這將是這樣被使用,假設Error是我的錯誤類:

auto result = do_stuff(); 
if (!is_success(result)) { 
    Error err = *result; 
    // ... 
} 

還是會因此類更爲強勁?

class MaybeError { 
    std::optional<Error> _error; 
public: 
    MaybeError(const Error& error) : _error(error) {} 
    constexpr MaybeError() : _error({}) {} 

    explicit operator bool const() { 
     return !(_error.operator bool()); 
    } 
    constexpr bool has_error() const { 
     return !(_error.has_value()); 
    } 

    constexpr Error& error() & { return _error.value(); } 
    constexpr const Error & error() const & { return _error.value(); } 
    constexpr Error&& error() && { return std::move(_error.value()); } 
    constexpr const Error&& error() const && { return std::move(_error.value()); } 

    constexpr const Error* operator->() const { return _error.operator->(); } 
    constexpr Error* operator->() { return _error.operator->(); } 
    constexpr const Error& operator*() const& { return _error.operator*(); } 
    constexpr Error& operator*() & { return _error.operator*(); } 
    constexpr const Error&& operator*() const&& { return std::move(_error.operator*()); } 
    constexpr Error&& operator*() && { return std::move(_error.operator*()); } 
}; 

這同樣可以使用在第一個例子:

auto result = do_stuff(); 
if (!result) { 
    Error err = *result; 
    // ... 
} 

什麼是最好的選擇嗎?我是否以正確的方式去做這件事?


編輯要明確的是,do_stuff功能被呼叫如果成功,不返回的對象。如果它總是成功沒有錯誤,它只是void do_stuff()


編輯2在評論克里斯蒂安·哈克建議使用一個簡單的結構較不超工程解決方案。

struct MaybeError { 
    std::optional<Error> error; 
}; 

這將是既簡單,解決我的擔心,人們會期望通過功能擺明,這是所要檢測的錯誤情況返回如果成功的話屬實。例如:

auto result = do_stuff(); 
if (result.error) { 
    Error e = *t.error; 
    // ... 
} 
+4

這看起來非常複雜,當使用異常時會更清晰和更簡單,並且涉及更少的代碼。 –

+2

我很樂意使用例外,但不幸的是,我的老闆對這個問題有着強烈的感受。 – ChrisD

+2

他是個白癡。良好C++代碼中的異常不是可選的。 –

回答

2

我玩了一下boost::expected就是suggested by Nicol Bolas。這似乎是非常好的這個用例:

#include <iostream> 
#include <system_error> 
#include <boost/expected/expected.hpp> 

using ExpectedVoid = boost::expected< void, std::error_code >; 
using ExpectedInt = boost::expected< int, std::error_code >; 

ExpectedVoid do_stuff(bool wantSuccess) { 
    if(wantSuccess) 
     return {}; 
    return boost::make_unexpected(std::make_error_code(std::errc::operation_canceled)); 
} 

ExpectedInt do_more_stuff(bool wantSuccess) { 
    if(wantSuccess) 
     return 42; 
    return boost::make_unexpected(std::make_error_code(std::errc::operation_canceled)); 
} 

int main() 
{ 
    for(bool wantSuccess : { false, true }) 
    { 
     if(auto res = do_stuff(wantSuccess)) 
      std::cout << "do_stuff successful!\n"; 
     else 
      std::cout << "do_stuff error: " << res.error() << "\n"; 
    } 

    std::cout << "\n"; 

    for(bool wantSuccess : { false, true }) 
    { 
     if(auto res = do_more_stuff(wantSuccess)) 
      std::cout << "do_more_stuff successful! Result: " << *res << "\n"; 
     else 
      std::cout << "do_more_stuff error: " << res.error() << "\n"; 
    } 

    return 0; 
} 

輸出:

do_stuff error: generic:105 
do_stuff successful! 

do_more_stuff error: generic:105 
do_more_stuff successful! Result: 42 

您可以從鏈接開始下載源代碼,從剛扔文件「包括」目錄將源文件放入您的boost文件夾中(「boost」子文件夾)。

2

有這種設計的類型。一個proposed for standardization (PDF)expected<T, E>。它基本上就像一個可以具有所需值或「錯誤代碼」的變體(如果您只想檢查過程是否成功,則T可以是void)。

當然,如果您有權訪問這樣的實現,您可以使用實際的variant。但expected有一個更好的界面,專爲此場景而設計。與C++ 17中的std::variant不同,建議的expected不能爲valueless_by_exception,因爲E必須是不可移動的類型(對於大多數錯誤代碼來說不完全是高位欄)。

+0

我唯一的問題是我的do_stuff函數不需要成功返回任何東西(請參閱我的編輯)。所以T型將是多餘的。我想我可以使用布爾,並始終將其設置爲true。 – ChrisD

+1

@ChrisD:這就是爲什麼'expected'允許你將'T'設置爲'void'的原因。 –

+0

實際上有一個[預期實現](https://github.com/ptal/expected) - 從快速瀏覽它真的很酷!在我的答案中看到示例用法。 – zett42

相關問題