0

我想使用 std::optional 作为 Maybe 类型,我担心我是否可以使用它来指示某些计算中的潜在失败,因此被观察为空选项。

例如,考虑:

// This function doesn't have state
std::optional<int> multiply_by_2(std::optional<int> ox) noexcept
{
    if (ox)
        return {ox.value() * 2};

    // empty input case
    return {};
}

int main()
{
    auto input_handle = get_stdin().lock();
    auto output_handle = get_stdout().lock();

    // This can panic the application, which is fine at this point
    // so just unwrap the optional and call it a day
    output_handle.println("Your marvellous calculation: {}", multiply_by_2(input_handle.get_line().as_int()).value());
}
  • 这是返回计算的有效技术吗?它可以避免一些异常膨胀吗?
  • 这会引起任何麻烦吗?
4

2 回答 2

4

这里似乎有几个问题。

1.std::optional在函数体中处理 a 是否安全noexcept

是的,只要T由 包裹的 typestd::optional是不可复制构造的。

在这段代码中:

if (ox)
    return {ox.value() * 2};

由于您在调用之前进行检查std::optional::value(),因此永远不会引发异常。话虽如此,既然您已经确定 ,ox.has_value()最好使用operator*

if (ox)
    return {*ox * 2};

这样编译器就不需要生成前置条件检查和throw std::bad_optional_access语句(在简单的情况下,编译器可以优化它,if (ox)但为什么要让编译器做更多的工作)。

2.这是返回计算的有效技术吗?它可以避免一些异常膨胀吗?

据推测,“异常膨胀”是指编译器需要生成的所有异常处理代码的二进制大小开销。如果出于某种原因您真的关心这一点,那么是的 - 该std::optional技术可以在没有错误发生时以更多开销为代价来避免这种膨胀。

std::optional这是您必须接受这种样式错误处理( 、、std::error_code结果等)的权衡。您同意在成功时获得持续的开销,以便在失败时获得持续的开销。另一方面,异常只会在失败时产生开销(时间和空间上的不确定性)。

在这种特殊情况下,您最好不要污染一个简单的函数,否则该函数不会因错误处理而失败。而是将其推迟给调用者。毕竟调用者可能已经知道该值存在。

3.这会造成什么麻烦吗?

这里的主要问题是缺少有关错误的任何信息。 std::optional无法传达操作失败的原因。在一个简单的应用程序中,这可能不是问题,但是一旦您开始编写更复杂的操作,追踪失败原因的问题就会变得很明显。

即使在此代码中,也有一些错误条件可能有助于正确报告:IO 错误和解析错误。然后在解析错误中:

  • 输入可能不是有效数字;
  • 数量可能超过 的范围int

假设您最终没有使用异常,请考虑使用建议的std::expectedOutcome library之类的东西。

我不建议只使用 a std::variant(至少不包装它),因为它没有传达错误处理的意图。此外,它不支持持有void

于 2019-09-24T09:11:54.967 回答
1

异常是 C++ 中的一个敏感话题,对此有几种不同的看法。我知道有一些标准化提案(c++23 或更高版本)正在改进它们,你是对的,它们不是零开销。

但是,任何系统都不会完全零开销,因为您确实需要在多个地方进行错误处理和检查。

对我来说,使用 std::optional 是完全错误的,因为没有任何信息:出错了,请附上调试器以找出原因。我更赞成具有值或失败信息的结构之类的异常。std:: variant想到了。当然,如果不报告它,您仍然需要一个调试器来了解出了什么问题。

也就是说,如果您不介意这一点,那么您的代码在功能和性能方面都会很好。我想知道的是:您是否需要使用可选作为参数?您当前一个接一个地链接,并且通过函数重载,您可以提供一个更小的实现:

// This function doesn't have state
 int multiply_by_2(int ox) noexcept
{
    return ox * 2;
}

// This function doesn't have state
std::optional<int> multiply_by_2(std::optional<int> ox) noexcept
{
    if (ox)
        return multiply_by_2(ox.value());

    // empty input case
    return std::nullopt;
}

这样,如果您的输入不会失败,您的开销就会减少。

最后:确保这些方法对调用者可见。由于您不检查错误路径,因此在发生故障时您将拥有 UB。我没有检查过,尽管编译器可能能够通过您的函数删除故障路径,因为它不能在确认程序中使用。

于 2019-09-24T06:12:19.410 回答