在 noexcept 函数的主体中处理 std::optional 是否安全?
Is it safe to handle a std::optional in the body of a noexcept function?
我想使用 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());
}
- 这是返回计算的有效技术吗?它能避免一些异常膨胀吗?
- 这会引起什么麻烦吗?
异常是 C++ 中的一个敏感话题,对此有多种不同的看法。我知道有标准化提案(c++23 或更高版本)正在改进它们,你是对的,它们不是零开销。
但是,没有系统会完全零开销,因为您确实需要在多个地方进行错误处理和检查。
对我来说,使用 std::optional 是完全错误的,因为没有任何信息: 出错了,请附上调试器以找出原因。
我更赞成具有值或故障信息的结构之类的异常。 std:: variant
浮现在脑海中。
当然,不报告它,你仍然需要一个调试器来了解哪里出了问题。
就是说,如果您不介意这一点,您的代码在功能和性能方面都没有问题。
我想知道的是:你需要使用 optional 作为参数吗?
您目前一个接一个地链接,并且通过函数重载,您可以提供一个更小的实现:
// 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。我没有检查过,尽管编译器可能能够通过您的函数删除失败路径,因为它不能在确认程序中使用。
这里好像有几个问题。
1. 在 noexcept
函数体中处理 std::optional
是否安全?
是的,只要被 std::optional
包装的类型 T
不可抛出复制构造。
在此代码中:
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. 这是返回计算的有效技术吗?它能避免一些异常膨胀吗?
据推测,"exception bloat" 是指编译器需要生成的所有异常处理代码的二进制大小开销。如果出于某种原因你真的关心这个,那么是的 - std::optional
技术可以在没有错误发生的情况下以更多的开销为代价避免这种膨胀。
这是您必须接受这种样式错误处理(std::optional
、std::error_code
、结果等)的权衡。您同意在成功时有持续的开销,以便在失败时获得持续的开销。另一方面,异常只会在失败时产生开销(时间上不确定且space)。
在这种特殊情况下,您最好不要污染一个简单的函数,否则它不会因错误处理而失败。而是将其推迟给调用者。毕竟调用者可能已经知道该值存在。
3.这会不会有什么麻烦?
这里的主要问题是缺少有关错误的任何信息。
std::optional
无法传达操作失败的原因。在一个普通的应用程序中,这可能不是问题,但是一旦您开始编写更复杂的操作,追踪失败原因的问题就会变得很明显。
即使在这段代码中,也有一些错误情况可能有助于适当地报告:IO 错误和解析错误。然后在解析错误中:
- 输入的数字可能无效;
- 数量可能超出
int
的范围。
假设您最终不使用异常,请考虑使用建议的 std::expected or the Outcome library.
我不建议只使用 std::variant
(至少在不包装它的情况下),因为它没有传达错误处理的意图。此外,它不支持持有 void
.
我想使用 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());
}
- 这是返回计算的有效技术吗?它能避免一些异常膨胀吗?
- 这会引起什么麻烦吗?
异常是 C++ 中的一个敏感话题,对此有多种不同的看法。我知道有标准化提案(c++23 或更高版本)正在改进它们,你是对的,它们不是零开销。
但是,没有系统会完全零开销,因为您确实需要在多个地方进行错误处理和检查。
对我来说,使用 std::optional 是完全错误的,因为没有任何信息: 出错了,请附上调试器以找出原因。
我更赞成具有值或故障信息的结构之类的异常。 std:: variant
浮现在脑海中。
当然,不报告它,你仍然需要一个调试器来了解哪里出了问题。
就是说,如果您不介意这一点,您的代码在功能和性能方面都没有问题。 我想知道的是:你需要使用 optional 作为参数吗? 您目前一个接一个地链接,并且通过函数重载,您可以提供一个更小的实现:
// 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。我没有检查过,尽管编译器可能能够通过您的函数删除失败路径,因为它不能在确认程序中使用。
这里好像有几个问题。
1. 在 noexcept
函数体中处理 std::optional
是否安全?
是的,只要被 std::optional
包装的类型 T
不可抛出复制构造。
在此代码中:
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. 这是返回计算的有效技术吗?它能避免一些异常膨胀吗?
据推测,"exception bloat" 是指编译器需要生成的所有异常处理代码的二进制大小开销。如果出于某种原因你真的关心这个,那么是的 - std::optional
技术可以在没有错误发生的情况下以更多的开销为代价避免这种膨胀。
这是您必须接受这种样式错误处理(std::optional
、std::error_code
、结果等)的权衡。您同意在成功时有持续的开销,以便在失败时获得持续的开销。另一方面,异常只会在失败时产生开销(时间上不确定且space)。
在这种特殊情况下,您最好不要污染一个简单的函数,否则它不会因错误处理而失败。而是将其推迟给调用者。毕竟调用者可能已经知道该值存在。
3.这会不会有什么麻烦?
这里的主要问题是缺少有关错误的任何信息。
std::optional
无法传达操作失败的原因。在一个普通的应用程序中,这可能不是问题,但是一旦您开始编写更复杂的操作,追踪失败原因的问题就会变得很明显。
即使在这段代码中,也有一些错误情况可能有助于适当地报告:IO 错误和解析错误。然后在解析错误中:
- 输入的数字可能无效;
- 数量可能超出
int
的范围。
假设您最终不使用异常,请考虑使用建议的 std::expected or the Outcome library.
我不建议只使用 std::variant
(至少在不包装它的情况下),因为它没有传达错误处理的意图。此外,它不支持持有 void
.