2017-02-26 111 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