std::expected适用于预期会失败且必须显式处理的同步操作(如解析配置、读取文件头),而非异常替代或optional升级;错误罕见时用异常,值可能不存在时用optional;需注意编译器支持、错误类型兼容性、链式处理及互操作边界。

std::expected 什么时候该用,什么时候别硬套
它不是 std::optional 的升级版,也不是异常的替代品——它是为“预期会失败、且调用方必须显式处理”的同步操作设计的。比如解析配置、读取文件头、序列化反序列化。如果错误是罕见的、不可恢复的(如内存耗尽),还是该抛异常;如果只是“值可能不存在”,std::optional 更轻量。
常见错误现象:std::expected<t e></t> 被当成万能容器塞进容器或作为函数返回值盲目泛化,结果编译报错一堆模板推导失败,或者 operator== 不可用、无法被 std::variant 持有。
- 只在函数契约明确区分“成功值”和“具体错误类型”时使用,例如
std::expected<:string std::errc></:string> - 避免嵌套:不要写
std::expected<:expected e>, E></:expected>,这说明控制流设计有问题 - 注意 C++23 标准才正式纳入,GCC 13+ / Clang 16+ / MSVC 19.35+ 才完整支持;老编译器得用 TartanLlama/expected 第三方实现,但接口不完全一致
怎么链式处理多个可能失败的操作(类似 Rust 的 ?)
C++ 没有内置的 ? 操作符,但可以用 and_then 和 or_else 模拟短路逻辑。关键不是“写得像 Rust”,而是避免层层 if (e.has_value()) 嵌套。
使用场景:连续调用三个函数,每个都返回 std::expected<t e></t>,任一失败就终止并透传错误。
立即学习“C++免费学习笔记(深入)”;
auto parse_config() -> std::expected<Config, std::errc> { /* ... */ }
auto load_schema(Config) -> std::expected<Schema, std::errc> { /* ... */ }
auto validate_data(Schema) -> std::expected<Data, std::errc> { /* ... */ }
<p>// 正确链式写法
auto result = parse_config()
.and_then([](Config c) { return load_schema(c); })
.and_then([](Schema s) { return validate_data(s); });</p>-
and_then只在has_value()为 true 时调用回调,回调必须返回std::expected(类型可变,但错误类型需兼容) - 错误类型不匹配?编译器直接报错,比如一个返回
std::errc,另一个返回std::string,就不能直接and_then - 想统一错误类型?用
map_error提前转换:e.map_error([](auto e) { return std::make_error_code(e); })
如何与现有错误处理机制(errno / 异常 / 返回码)互操作
std::expected 不排斥其他方式,但混用时边界必须清晰。最常见坑是:把 throw 包进 std::expected 返回值里,结果调用方忘了检查 has_value() 就直接解包,崩溃。
性能影响:std::expected 是零成本抽象,无动态分配、无虚函数,但若错误类型较大(比如含 std::string),移动开销比 int 级错误码高。
- 封装 C 函数:用
std::expected<int std::errc></int>包装open()或read(),出错时用std::make_unexpected(std::errc(errno)) - 捕获异常再转成 expected:在边界函数里用
try/catch,把异常信息转为枚举或std::error_code,不要存原始异常对象(生命周期难管理) - 别把
std::expected<void e></void>当作“无返回值的异常安全包装”——它不提供栈展开保证,异常仍需按常规处理
容易被忽略的细节:拷贝、移动与比较行为
std::expected 默认是可移动的,但是否可拷贝取决于 T 和 E 是否可拷贝。很多人在容器里存它,然后尝试 std::vector<:expected error>> v; v.push_back(...);</:expected>,结果编译失败——因为 BigStruct 没定义拷贝构造。
兼容性影响:C++23 的 std::expected 不提供 operator==,哪怕 T 和 E 都可比较。想比较两个 expected?得手动写逻辑:
bool eq(const std::expected<int, std::errc>& a, const std::expected<int, std::errc>& b) {
if (a.has_value() != b.has_value()) return false;
if (a.has_value()) return a.value() == b.value();
return a.error() == b.error();
}
- 值语义陷阱:
std::expected是值类型,传参建议用 const 引用,尤其当T或E较大时 - 没
std::hash特化,不能直接放进std::unordered_map - 调试时注意:GDB/Lldb 对
std::expected的打印支持有限,有时需手动调用has_value()或value()查看
事情说清了就结束。真正难的不是语法,是判断哪些路径该用 std::expected、哪些该让调用方自己 try/catch、哪些干脆就该用返回码加文档注释。这个边界,得靠实际 debug 过三次以上超时、权限、格式错误才能摸准。









