如何从函数 return 成功或错误对象?

How to return success or an error object from functions?

我希望我的函数 return 指示成功或描述失败性质的对象。我通常会为此使用异常,但有人告诉我不要将它们用于公共代码路径,并且由于各种原因,预计这组函数会经常失败。

我的想法是使用 C++17 的 std::optional,因为在不需要时我不必 return 一个完整的错误对象。所以对于可选的,如果函数没有成功,它 returns 是错误对象,否则可选的是空的。问题在于它反转了对 returned 值的期望(即 true 通常表示成功而不是失败)。

我可以让人们使用 is_success 函数,它会像这样使用,假设 Error 是我的错误 class:

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

或者结果 class 会更可靠吗?

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;
    // ...
}

最好的选择是什么?我这样做正确吗?


Edit 需要说明的是,被调用的 do_stuff 函数如果成功则不会 return 一个对象。如果它总是成功而没有错误,那就是 void do_stuff().


编辑 2 在评论中,Christian Hackl 建议使用一个简单的结构来减少过度设计的解决方案。

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

这会更简单,并且可以解决我的担忧,即如果成功,人们会期望函数 return 为真,方法是明确表示这是正在测试的错误条件。例如:

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

有专门为此设计的类型。一个 proposed for standardization (PDF)expected<T, E>。它基本上就像一个可以具有所需值或 "error code" 的变体(如果您只想检查进程是否成功,T 可以是 void)。

当然,如果您有权访问此类实现,则可以使用 actual variant。但是 expected 有一个更好的界面,专为这种情况而设计。与 C++17 中的 std::variant 不同,提议的 expected 不能是 valueless_by_exception,因为 E 必须是不可移动的类型(对于大多数错误代码)。

我玩了一下 boost::expected that was 。对于这个用例来说似乎真的很好:

#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

你可以从开头的link下载源码,把源码"include"目录下的文件扔到你的boost include目录("boost"子文件夹) .