如何从函数 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"子文件夹) .
我希望我的函数 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"子文件夹) .