std::expected
以及什么时候应该使用异常?以这个函数为例:
int parse_int(std::string_view str) {
if (str.empty()) {
throw std::invalid_argument("string must not be empty");
}
/* ... */
if (/* result too large */) {
throw std::out_of_range("value exceeds maximum for int");
}
return result;
}
我想在使用这个函数时区分不同的错误,所以我可以抛出不同类型的异常,这很有用。不过,我也可以用
std::expected
来做到这一点:
enum class parse_error {
empty_string,
invalid_format,
out_of_range
};
std::expected<int, parse_error> parse_int(std::string_view str) noexcept {
if (str.empty()) {
return std::unexpected(parse_error::empty_string);
}
/* ... */
if (/* result too large */) {
return std::unexpected(parse_error::out_of_range);
}
return result;
}
是否有任何理由在例外情况(性能、代码大小、编译速度、ABI)上使用
std::expected
,或者只是风格偏好?
首先,无论您计划使用什么错误处理策略 - 在给定项目的一开始就建立它 - 请参阅 E.1:在设计早期制定错误处理策略。因为“稍后”改变这一策略的想法很可能会导致有两种策略——旧的和新的。
有时,选择很简单 - 当出于某种原因在给定项目中不允许出现异常时 - 只需使用
std::expected
。
提出一种适合所有需求的错误处理策略确实很难(我想说,不可能)。我只能在这里提出我尝试遵循的一项建议:
可能的错误处理策略之一,可以称为遵循名称:
exceptions
。当 throw 指令真正被调用的可能性很低时。std::expected
有时,这可能意味着这两种方式都在一个函数中使用 - 就像函数返回
std::excpected<T, E>
一样,这是预期的 E
错误,但该函数没有标记为 noexcept
因为它可能会在一些非常罕见的情况下抛出。但是,如果您建立的错误策略是返回 std::expected<T,E>
的函数永远不会抛出 - 那么您需要让这个“意外”错误成为 E
的变体。