std::expected 不能用 std::optional 替代错误处理,因前者强制携带可区分、可检查的错误值,后者仅表示“是否存在”,无法传达失败原因;std::expected 支持类型化错误处理、链式传播与业务语义表达。

std::expected 不能用 std::optional 替代错误处理,因为前者强制携带可区分、可检查的错误值,后者只表达“有没有”,不回答“为什么没有”。
std::optional::has_value() 到底在判断什么
它只判断值是否存在,不提供任何失败线索。比如 std::optional 返回 std::nullopt,调用方完全无法知道这是参数非法、IO 超时、还是内存分配失败——所有失败被压缩成同一个空状态。
而 std::expected 返回 std::unexpected("JSON parse error at line 42"),错误信息直接可用;若用枚举 enum class ParseError { InvalidToken, UnexpectedEOF },还能 switch 处理,日志和监控也能按类型打点。
-
std::optional的空值是语义模糊的“缺失”,适合表示“可选配置项未设置”这类本就允许为空的场景 -
std::expected的std::unexpected(E)是语义明确的“失败”,适用于“本该成功但出错了”的路径,如文件读取、网络响应解析 - E 类型必须可复制/可移动,且不能是
void(否则该用std::optional)
and_then 链式调用为什么 std::optional 会静默中断
std::optional::and_then 在值为空时直接返回空,不传播原因;而 std::expected::and_then 在失败时保留原始错误,且支持 or_else 做差异化恢复。
更关键的是:链中任意一步返回非 std::expected 类型(比如 bool 或 std::optional),编译器立刻报错,例如:
no matching function for call to ‘and_then’
这反而成了保护机制——它逼你把每步都定义成显式失败可传递的接口。
- 正确写法:
parse_json(s).and_then(validate).and_then(save_to_db),三者都返回std::expected - 错误写法:
validate返回bool→ 链断裂,要么编译失败,要么逻辑跳过失败分支,变成“看似成功实则丢数据” -
and_then的 lambda 是值捕获,别在里面抛异常;若E构造失败(如std::string构造时抛std::bad_alloc),会直接传播出去
std::expected 不是异常的自动翻译器
它不捕获异常,也不自动包装抛出的异常。写 throw std::runtime_error("oops") 不会变成 std::unexpected,而是直接终止程序。
想用它替代 try/catch,必须手动封装:
- 先关掉底层函数的异常开关(如
std::ifstream的exceptions()) - 改用状态查询(如
rdstate())或返回码 - 在
catch块里显式构造std::unexpected(E) - 错误类型选
std::error_code时,注意配对正确 category:文件系统错误要用std::system_category(),不是std::generic_category()
业务语义表达力是最大分水岭
用 std::error_code 表达系统级错误没问题,但它对业务逻辑太弱。比如 std::errc::permission_denied 和 “用户已被风控” 完全是两回事,前者是 OS 层面,后者是领域规则。
真正健壮的业务层错误处理,应该用自定义枚举或带字段的结构体:
-
enum class OrderError { NotFound, InsufficientBalance, InvalidState }—— 可switch、可序列化、无拼写错误 - 错误对象带
trace_id、timestamp字段,方便调试与审计 - 避免把
std::expected当万能兜底,它解决不了业务归因问题
最常被忽略的一点:std::expected 的价值不在“不用异常”,而在于让错误成为可命名、可组合、可测试的一等公民——不是靠文档猜,而是靠类型系统推。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











