如何从函数中返回成功或错误对象?

时间:2017-02-26 15:38:19

标签: c++ c++17

我希望我的函数返回成功指示或描述失败性质的对象。我通常会使用例外情况,但我已被告知不要将它们用于公共代码路径,并且由于各种原因,这些功能可能会经常失败。

我的想法是使用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 在评论中,Christian Hackl建议使用简单的结构来减少过度设计的解决方案。

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

这将更简单,并解决我的担心,人们会希望函数在成功时通过明确表示它是正在测试的错误条件而返回true。 E.g:

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

2 个答案:

答案 0 :(得分:2)

有为此设计的类型。一个proposed for standardization (PDF)expected<T, E>。它基本上就像一个变量,它可以具有所需的值或错误代码&#34; (T如果您只想查看某个流程是否成功,则可以是void

当然,如果您有权访问此类实现,则可以使用实际 variant。但expected有一个更好的界面,专为此场景设计。与C ++ 17中的std::variant不同,建议的expected不能是valueless_by_exception,因为E必须是一个不可移动的类型(不是一个很高的条形码)大多数错误代码)。

答案 1 :(得分: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

您可以从开头的链接下载源代码,然后从&#34; include&#34;中删除文件。进入你的boost include目录的源目录(&#34; boost&#34; sub文件夹)。