std::expected 是 C++23 引入的类型,用于显式表达“成功(含 T)或失败(含 E)”的返回值;而 std::optional 仅表示“有或无值”,不携带错误信息。

std::expected 是什么,和 std::optional 有什么区别
std::expected 是 C++23 引入的类型,用于显式表达「可能成功也可能失败」的返回值。它不是用来替代 std::optional 的——后者只表示「有或没有值」,不携带错误信息;而 std::expected 明确区分:成功时含 T,失败时含 E(比如 std::error_code 或自定义错误枚举)。
常见错误现象:有人试图用 std::optional 表示「计算结果或出错」,但调用方无法知道为什么失败,只能靠额外文档或全局错误码推断。
-
std::expected的E类型必须是可复制/可移动的,且不能是void(否则该用std::optional) - 不支持隐式转换到 bool,必须显式调用
.has_value()或用if (auto res = foo(); res)这类结构判断 - 它不接管异常语义:构造失败值不会抛异常,但若
E构造本身抛异常,则传播出去
怎么从函数返回 std::expected 并正确消费
典型使用场景:系统调用封装、文件读取、JSON 解析等可能失败但又不想用异常的路径。
std::expectedsafe_divide(int a, int b) { if (b == 0) { return std::unexpected(std::make_error_code(std::errc::invalid_argument)); } return a / b; }
消费时别直接解包:
立即学习“C++免费学习笔记(深入)”;
- ❌ 错误写法:
int x = *res;—— 若res失败会std::terminate - ✅ 推荐写法:用
if (res)判断再访问res.value(),或用res.value_or(42)提供默认值 - 更安全的模式是用
and_then链式处理(C++23 支持),类似 Rust 的?:auto result = parse_json(s).and_then(validate).and_then(save_to_db);
注意:and_then 要求每个函数都返回 std::expected,否则链会断。
std::expected 和异常、错误码、返回结构体比有什么代价
性能上,std::expected 通常比异常轻量(无栈展开开销),但比纯错误码多一次拷贝/移动(尤其 E 较大时)。兼容性方面:仅 C++23 及以上标准可用,GCC 13+、Clang 16+、MSVC 19.35+ 才完整支持。
常见坑:
- 误以为
std::expected的内存布局和union一样紧凑——实际实现可能带额外标志位,不要做reinterpret_cast或 memcpy 操作 - 把
std::error_code当作万能错误类型:它不携带上下文字符串,调试困难;建议搭配std::string或自定义错误类型(需满足三法则) - 忘记
std::expected不提供operator==默认实现,比较两个expected需手动展开判断
如何在 C++20 项目里模拟 std::expected 行为
如果不能升级到 C++23,别硬套第三方库(如 tl::expected),容易引入 ABI 或 SFINAE 兼容问题。更稳妥的做法是:
- 用结构体手动封装:
struct Result { bool ok; T value; E error; };,但要自己实现value()、error()、has_value()等接口 - 或退回到
std::pair<:optional>, std::optional,虽啰嗦但清晰、无依赖、零成本抽象> - 真正需要链式处理时,优先考虑重构逻辑:把失败路径提前
return,避免深层嵌套判断
最常被忽略的一点:很多人只关注「怎么返回错误」,却没想清楚「谁负责销毁错误值里的资源」——比如 E 是一个打开的文件句柄,std::expected 不会自动 close,得在 if (!res) 分支里显式清理。










