是否有
std::optional
的替代方案,我不必将 result
作为参数传递给函数。我希望函数不修改其参数(更加纯粹/不可变)。
TL;博士
std::optional
的问题似乎是我们丢失了有关错误的信息。该函数返回一个值或空值,因此您无法判断出了什么问题。
使用std::可选
std::optional<std::string> doSomething() {
std::string value;
int rc = callApi(value);
if (rc == 0) {
//do some processing on value
return value;
}
return std::nullopt;
}
//calling the function seems much more pure/cleaner than when passing a result parameter.
没有 std::可选
int doSomething(std::string& result) {
std::string value;
int rc = callApi(value);
if (rc == 0) { //if no error
//do some processing on value and set result = value
result = value;
}
return rc;
}
看来您要找的是
std::expected
。
更新:从 C++23 开始,它位于 C++ 标准库中
...它实际上已经不在标准中,但是:
嗯...简而言之:当您想要返回某个值或某种失败/错误描述符时,您只需将它们模板化,比如 T、E,这些就是模板参数:
std::expected<T, E>
。由于 T 和 E 是不相交的类型,因此您知道从函数中返回了其中的哪一个。
这是您的函数,适用于您的 API 似乎具有的错误类型:namespace my_api {
using error_t = int;
enum : error_t { success = 0, invalid_input = 1, /* etc. */ };
} // namespace my_api
std::expected<std::string, my_api::error_t> doSomething() {
std::string value;
my_api::error_t rc = callApi(value);
if (rc != success) { return rc; }
//do some processing on value
return value;
}
当然,
my_api
命名空间不是我建议的一部分,它只是一个说明,因为您没有指出您希望如何传达错误。事实上,你甚至可以添加:
namespace my_api {
template <typename T>
using expected = std::expected<T, error_t>;
} // namespace my_api
然后你的函数签名变成:
my_api::expected<std::string> doSomething();
std::variant
是一个类型安全的联合,允许您返回一组固定类型中的一个。在这种情况下,您需要一个
std::variant<std::string, int>
使用std::variant
std::variant<std::string, int>doSomething(){
std::string value;
int rc = callApi(value);
if(rc == 0){ //if no error
//do some processing on value
return value;
}
return rc;
}
std::optional
无意返回错误。它是一个用于“是否有价值”的非常简单概念的工具。来自 cppreference:
在任何给定时间点的任何可选实例要么包含值,要么不包含值。如果您正在编写 C++ 函数,并且错误不是您业务逻辑的一部分(即,当您收到错误而不是可用结果时,您不会继续操作),只需抛出异常即可。
std::runtime_error
很适合你。或者您可以使用
std::error_code
。
如果错误很少发生,那么抛出异常在性能方面是非常好的。在这里,您可以在失败时抛出错误值(即来自
callApi
的返回代码),然后将对
doSomething()
的任何调用包装在 try ... catch
块中。std::string doSomething(){
std::string value;
int rc = callApi(value);
if(rc) throw rc;
//do some processing on value
return value;
}
选项 2:返回结构体在这里,您创建一个包含
std::string
和返回码的结构体;这允许对错误信息和成功返回的字符串进行编码。
struct ReturnString {
std::string value;
int rc;
}
ReturnString doSomething(){
ReturnString return_value;
std::string & value = return_value.value;
int & rc = return_value.rc;
rc = callApi(value);
if(rc) return return_value;
//do some processing on value
return return_value;
}
然后,调用代码可以检查
rc
的值,如果非零则适当处理错误,或者如果为 0,则使用字符串。
您甚至可以使用通用结构,其中值的类型是模板参数。您还可以添加辅助函数来检查rc
的值,以便在适当的情况下为您提供字符串内容。不过,您可以使用标准库中已经存在的类似内容,而不是执行所有这些操作:
选项 3:使用变体
std::variant
表示类型安全联合,因此可以保存返回码或字符串。辅助函数可用于确定它保存的是哪一个。所以,你可能会这样做:
std::variant<int, std::string> doSomething(){
std::variant<int, std::string> return_value;
std::string value;
int rc = callApi(value);
if(rc) return rc;
//do some processing on value
return return_value;
}
然后,从您的调用函数中,您可以检查变体以查看它所持有的类型,并相应地继续:
auto v = doSomething();
int rc;
if(std::holds_alternative<int>(v)) {
rc = std::get<int>(v);
//Process error accordingly
if(std::holds_alternative<std::string>(v)) {
std::cout << "String returned was " << std::get<std::string>(v) << std::endl;
}