std::expected 不能用 std::optional 替代,因为后者仅表示“有无值”,不携带错误信息;前者强制提供具体错误类型 e,支持编译时检查、链式处理 and_then、线程安全且无栈展开开销。

std::expected 为什么不能用 std::optional 替代
因为 std::optional 根本不表达“错误”,只表达“有没有值”。调用方看到 std::nullopt,完全不知道是参数错、磁盘满、还是内存分配失败——所有失败都被压缩成一个空状态。
而 std::expected<t e></t> 强制你提供具体错误类型 E,比如 std::error_code、std::string 或自定义枚举 ParseError。编译器会检查你是否处理了失败分支,不会让你靠注释或文档猜原因。
-
std::optional<int></int>返回std::nullopt→ 调用方只能写if (!opt) { /* ??? */ } -
std::expected<int std::string></int>返回std::unexpected("JSON parse error at line 42")→ 调用方可直接读取result.error() -
E类型必须可复制/可移动,且不能是void(否则该用std::optional)
and_then 链式调用失败时静默中断的常见原因
and_then 不是万能胶水,它只接受返回 std::expected 的函数。中间任何一步返回 bool、std::optional 或裸值,链就断了:要么编译报错,要么逻辑跳过失败路径,变成“看似成功实则丢数据”。
典型翻车现场:parse_json(s).and_then(validate).and_then(save_to_db),其中 validate 如果返回 bool,编译器会报类似 "no matching function for call to ‘and_then’"。
- 每个环节都必须返回
std::expected,类型要一致(比如全是std::expected<t std::string></t>) -
and_then的 lambda 是值捕获,别在里面抛异常;若E构造失败(如std::string构造时抛std::bad_alloc),会直接传播出去 - 不要混用
std::optional::and_then和std::expected::and_then—— 它们语义不同,接口不兼容
std::expected 在性能敏感场景下比异常更可靠
异常的栈展开开销在实时系统、游戏引擎或高频网络服务里不可接受。std::expected 没有运行时控制流跳转,所有错误路径都是普通分支,CPU 分支预测友好,LLVM 和 GCC 都能很好优化。
但它不是零成本:std::expected 对象大小至少是 sizeof(T) + sizeof(E) + 1 字节(含状态标记),而异常对象需动态分配。如果 E 很大(比如带长字符串的错误信息),要考虑缓存局部性。
- 禁用异常的构建环境(如嵌入式、内核模块)中,
std::expected是唯一能携带上下文的合规方案 - 对比
errno:后者是全局变量,多线程下必须加锁或用thread_local,std::expected天然线程安全 - 别把
std::expected<void e></void>当成“无返回值的错误包装”——它合法但冗余,此时应直接用std::expected<bool e></bool>或std::expected<:monostate e></:monostate>
std::expected 的 error() 方法和 has_value() 判断容易忽略的细节
std::expected 不支持隐式转换到 bool,这点和 std::optional 不同。写 if (result) 是编译错误,必须显式调用 .has_value() 或用结构化绑定解包。
另外,.error() 只能在 !has_value() 为真时安全调用;否则行为未定义。调试时容易漏掉这个前提,导致崩溃。
- 推荐写法:
if (result.has_value()) { use(*result); } else { log(result.error()); } - 结构化绑定更安全:
auto [val, err] = result; if (val) { /* success */ } else { /* failure, err is valid */ } -
value()和error()都不检查状态,出错时不会抛异常——这是设计选择,不是 bug
最常被绕过的点是:错误类型 E 的构造成本。如果每次失败都 new 一个长字符串或 deep-copy 一个复杂结构体,std::expected 就从“轻量替代异常”变成“更重的异常”。选 E 时优先考虑 std::error_code 或小枚举,字符串上下文按需延迟构造。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











