std::expected 是 C++23 引入的用于显式表达“可能成功或失败”的同步错误处理类型,适用于解析配置、打开本地文件等可预测错误场景,不适用于 I/O 等需异常或回调的异步/复杂错误情形。

std::expected 是什么,什么时候该用它
std::expected 是 C++23 引入的标准库类型,用于显式表达“可能成功、可能失败”的返回值,替代裸 std::pair<T, bool> 或自定义错误包装类。它不是万能的——只适合**同步、单点、可预测错误**场景,比如解析配置、打开本地文件、调用纯计算函数。不适合 I/O 等待、网络超时、异常传播链长的逻辑,那种情况仍应优先用异常或回调。
常见误用是把它当 Result<T, E> 一样层层 map / and_then,但标准库目前没提供这些组合子(C++26 才计划加入),硬写会冗长且易错。
如何构造和检查 std::expected 的值与错误
构造分两种:成功路径用 std::expected<int, std::string>{42},失败路径用 std::expected<int, std::string>{std::unexpect, "file not found"}。注意 std::unexpect 是必需的标记,漏写会编译失败。
检查状态不能靠 if (e) 这种隐式转换(虽然允许,但语义模糊);推荐明确用:
立即学习“C++免费学习笔记(深入)”;
-
e.has_value()判断是否成功 -
e.value()获取值(失败时抛std::bad_expected_access) -
e.error()获取错误(成功时同样抛异常)
更安全的做法是配合 if (e) { use(e.value()); } else { handle(e.error()); },避免意外崩溃。
std::expected 与异常的关键区别在哪
核心差异在于控制流是否“跳出当前栈帧”:std::expected 把错误当作数据传递,调用者必须显式分支处理;异常则自动向上冒泡,可能跳过中间资源清理逻辑。
这意味着:
- 用
std::expected时,RAII 对象析构仍按预期执行(比如std::ofstream关闭文件) - 但你得自己确保每个分支都覆盖错误路径,漏写
else就等于静默丢弃错误 - 跨 DLL 边界或 ABI 不稳定时,
std::expected比异常更可靠(异常类型需 ABI 兼容)
一个典型陷阱:把 std::expected 和 throw 混用在同一函数里,会导致错误处理逻辑割裂,后期维护者很难判断该查 .error() 还是捕获异常。
实际函数签名怎么设计才不踩坑
返回 std::expected 的函数,错误类型必须满足可复制/可移动,且不能是 void(C++23 要求错误类型非 void)。常见错误是直接用 std::error_code —— 它虽轻量,但语义弱;更推荐自定义枚举类(如 enum class ParseError { InvalidFormat, OutOfRange };)或短字符串(std::string_view 更好,避免堆分配)。
参数设计上,避免让调用方传入 std::expected& 做输出参数——这破坏了函数纯度,也违背 std::expected “返回即决策”的设计意图。所有输入应通过常规参数传入,输出只走返回值。
如果函数内部可能触发异常(比如 std::stoi 抛 std::invalid_argument),别在 catch 里简单转成 std::unexpected 就完事;要先做上下文补充(如记录原始字符串),否则错误信息丢失严重。


















