std::expected适合替代文件打开、json解析、配置加载等预期可能失败且需显式处理的同步错误码模式;不适用于异常拦截或错误类型无关的场景,要求错误类型可复制、可比对,并严格配对std::error_code或自定义枚举。

std::expected 适合替代哪些传统错误处理模式
当你有一串连续调用,每个都可能失败(比如文件打开 → 解析 JSON → 验证字段 → 写入数据库),且每个失败原因不同、需要区分处理时,std::expected 比 throw 更轻量,比手动传 std::pair<bool t></bool> 或自定义结果类型更标准。它不是万能的——如果错误路径极少、或错误类型完全无关(比如网络超时和磁盘满混在一起),反而会增加心智负担。
典型适用场景:链式 I/O 或配置加载逻辑,其中每步依赖前一步成功,且需保留具体错误信息(如 std::errc::no_such_file_or_directory 或自定义枚举)。
如何避免 map/and_then 嵌套过深导致可读性下降
std::expected 的 and_then 和 map 容易写成“金字塔”结构,尤其当分支逻辑变多时。这不是语法限制,而是惯性写法问题。
- 优先用
auto提前解包:先检查e.has_value(),再用*e取值,比连写三四个and_then更直觉 - 把中间步骤封装成命名函数,返回
std::expected<t e></t>,而不是在 lambda 里塞逻辑 - 对“可选但非关键”的步骤,用
value_or或transform替代and_then,避免强制链式
例如解析配置后默认填充字段,不用 e.and_then(parse).and_then(fill_defaults),而写成:
auto config = parse(json_str); if (!config) return config; config.value().fill_defaults(); // 直接改原值 return config;
std::expected 的移动语义陷阱在哪
当 std::expected<t e></t> 中的 T 是不可移动类型(如含 const 成员、或显式删除了移动构造函数),编译会直接报错,错误信息常指向 std::unexpected 构造失败,容易误判。
- 确认
T和E都满足std::is_move_constructible_v;若不行,考虑包装一层std::unique_ptr<t></t> - 返回局部对象时,确保编译器能执行 NRVO 或移动优化;否则显式用
std::move(x),但注意不要 move 已被value()访问过的对象 - 不要对
std::expected对象本身做std::move后再访问value()—— 它变成未定义行为,不是空值检查就能防住的
与现有 error_code / exception 混用时的关键边界
不能直接把 std::error_code 当作 E 类型塞进 std::expected<int std::error_code></int> 就完事。C++23 标准没要求 std::error_code 满足 std::equality_comparable,某些实现里比较两个 std::error_code 会静默失败。
- 推荐用
enum class作为E,显式映射到std::error_code(比如to_error_code(my_err)) - 若必须用
std::error_code,确保所有比较操作(如e.error() == std::errc::invalid_argument)在目标标准库版本中实际可用 - 混合使用
throw时,别在and_then的 lambda 里 throw;异常会跳出预期流程,std::expected不捕获它
真正麻烦的是跨模块边界:一个函数返回 std::expected,调用方习惯用 try/catch,这时候得靠文档或静态断言明确约定错误传播方式,而不是指望运行时兼容。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











