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

C++ 如何从函数返回成功或错误对象?,c++,c++17,C++,C++17,我希望函数返回成功的指示或描述失败性质的对象。我通常会为此使用异常,但有人告诉我不要将它们用于公共代码路径,并且由于各种原因,这组函数可能会经常失败 我的想法是使用C++17的std::optional,因为我不必在不需要时返回完整的错误对象。因此,对于optional,如果函数没有成功,它将返回error对象,否则optional为空。问题在于,它颠倒了对返回值的预期(即true通常表示成功而不是失败) 我可以让人们使用is_success函数,假设Error是我的错误类别,该函数的用法如下:

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

我的想法是使用C++17的
std::optional
,因为我不必在不需要时返回完整的错误对象。因此,对于optional,如果函数没有成功,它将返回error对象,否则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*()); }
};
最好的选择是什么?我这样做对吗


Edit要清楚,正在调用的
do_stuff
函数如果成功,则不会返回对象。如果它总是成功无误,那么它就是
void do\u stuff()


编辑2在Christian Hackl的评论中,他建议使用一个简单的结构设计一个不太过分的解决方案

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

有专门为此设计的类型。一个是预期的
。它基本上类似于一个变量,可以有所需的值,也可以有“错误代码”(
T
可以是
void
,如果您只想检查一个过程是否成功)


当然,如果您有权访问这样的实现,您可以使用实际的
变体。但是
expected
有一个更好的界面,是为这个场景设计的。与C++17中的
std::variant
不同,提议的
预期的
不能是
无价值的,因为
E
必须是一个不可移动的类型(对于大多数错误代码来说不完全是一个高条)。

我对它进行了一些讨论。对于这个用例来说似乎真的很好:

#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;
}

您可以从开头的链接下载源代码,只需将源代码的“include”目录中的文件放入boost include目录(“boost”子文件夹)。

这似乎非常复杂,当使用异常时,会更干净、更简单,涉及的代码更少。我很想使用异常,但不幸的是,我的老板对此有强烈的感觉。他是个白痴。异常在好的C++代码中不是可选的。异常并不总是合适的解决方案,有些地方甚至编译为 FNO异常,因此甚至不可能使用异常。您可以查看为C++20建议的
预期的
类型。可以找到当前的参考实现。它类似于Haskell@NeilButterworth:对于函数的直接调用方不处理函数中发生的错误的用例,特别存在异常。如果调用方能够并且应该处理错误,那么返回值是C++中的最佳实践,因此老板不是一个白痴。当然,OP提出的
MaybeError
类非常复杂,但我想这只是工程上的问题,一个简单的
struct MaybeError{bool has_error;std::optional error;}甚至只是
struct MaybeError{std::optional error;}就足够了。我唯一的问题是do_stuff函数不需要在成功时返回任何内容(请参见我的编辑)。因此,T型将是冗余的。我想我可以使用bool,并在成功时始终将其设置为true。@ChrisD:这就是为什么
expected
允许您将
T
设置为
void
。事实上,从快速看,它确实很酷!参见我答案中的示例用法。
auto result = do_stuff();
if (result.error) {
    Error e = *t.error;
    // ...
}
#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