std::Optional 的替代品,可以报告失败原因

问题描述 投票:0回答:4

是否有

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;
}
c++ c++17
4个回答
4
投票

看来您要找的是

std::expected


更新:从 C++23 开始,它位于 C++ 标准库中


...它实际上已经不在标准中,但是:

  • 您可以阅读该提案的最新版本,P0323 修订版 10
  • 你可以用它!或者更确切地说,是由才华横溢的 Sy Brand / TartanLlama 实现的,包括一些不错的扩展,我不会详细介绍。
  • 但是——那是什么?

嗯...简而言之:当您想要返回某个值或某种失败/错误描述符时,您只需将它们模板化,比如 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();

另请参阅:
C++ 中的 std::expected 是什么?


1
投票
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;
 }



1
投票
std::optional

无意返回错误。它是一个用于“是否有价值”的非常简单概念的工具。来自 cppreference:


在任何给定时间点的任何可选实例要么包含值,要么不包含值。

如果您正在编写 C++ 函数,并且错误不是您业务逻辑的一部分(即,当您收到错误而不是可用结果时,您不会继续操作),只需抛出异常即可。
std::runtime_error

很适合你。或者您可以使用

std::error_code

如果错误很少发生,那么抛出异常在性能方面是非常好的。
  • 抛出异常可以简化您的过程逻辑。您无需检查每个结果中的错误代码,而是捕获一个异常。

0
投票

选项 1:抛出错误

在这里,您可以在失败时抛出错误值(即来自

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

© www.soinside.com 2019 - 2024. All rights reserved.