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