std::expected用于预期可能失败的值传递,异常用于意外不可恢复的错误;判断标准是失败是否属于函数正常契约:文件打开失败等应使用std::expected,内存分配失败等应抛异常。

std::expected 不是异常处理的替代品,它和 throw/catch 解决的是不同层次的问题:前者用于**预期可能失败的值传递**,后者用于**意外、不可恢复的错误中断**。混用或强行用 std::expected 替代异常,反而会让错误流混乱。
什么时候该用 std::expected 而不是 throw?
核心判断标准是「这个失败是否属于函数正常契约的一部分」。
- ✅ 适合:
std::expected—— 例如文件打开失败(std::filesystem::status)、字符串转数字失败(std::from_chars)、网络请求返回 404 —— 这些是调用者**必须主动检查**的常规分支 - ❌ 不适合:
std::expected—— 例如内存分配失败(new抛std::bad_alloc)、栈溢出、std::vector::at越界 —— 这些是程序逻辑缺陷或资源耗尽,应由异常中止并向上报告 - ⚠️ 危险信号:如果写
if (auto r = f(); !r.has_value()) throw std::runtime_error(r.error().message());,说明你本该直接抛异常,而非绕一圈包装再解包
std::expected 的构造与错误提取容易踩哪些坑?
常见错误不是语法问题,而是语义误用:
- 别用
std::string存错误信息 ——std::expected<int std::error_code></int>比std::expected<int std::string></int>更轻量、可比、可跨模块传递;std::error_code已经覆盖系统/POSIX/标准库错误域 - 别在
value()上裸调用 —— 它在无值时抛std::bad_expected_access,等价于又引入了异常分支;应先用has_value()或直接用and_then/or_else - 注意移动语义:若
T(成功类型)不可移动,std::expected<t e></t>就不能被移动;同理,E也需满足可复制或可移动约束,否则编译失败
如何链式处理多个 std::expected 返回值?
避免嵌套 if,优先用 and_then 和 or_else 实现扁平化错误传播:
立即学习“C++免费学习笔记(深入)”;
auto parse_config() -> std::expected<Config, std::error_code> {
auto content = read_file("config.json"); // → expected<std::string, ec>
if (!content) return content; // 直接透传错误
return parse_json(content.value()); // → expected<Config, ec>
}
// 改成:
auto parse_config() -> std::expected<Config, std::error_code> {
return read_file("config.json")
.and_then(parse_json); // parse_json 接收 std::string,返回 expected<Config, ec>
}
关键点:
-
and_then的回调函数必须返回std::expected(同错误类型),否则编译不过 -
or_else用于错误恢复:比如 fallback 到默认配置,此时回调应返回std::expected<t e></t>,而不是裸T - 不支持像 Rust 那样自动推导错误类型合并;若中间步骤错误类型不同(如
std::error_codevsint),需手动转换或统一错误类型
为什么 C++23 的 std::expected 还不能完全替代 Result 类型生态?
标准版目前缺失几个工程中高频需要的能力:
- 没有
map/map_error(仅提供and_then/or_else),想对成功值做变换还得手动解包再重包 - 不支持从
std::expected<void e></void>隐式转为std::expected<t e></t>(即 no-alloc effect 类型),导致 I/O 类函数难以统一返回风格 - 没有类似
unwrap_or_else的便捷取值接口,常用模式仍需写r.has_value() ? r.value() : fallback() - 部分编译器(如 GCC 13.2)对
std::expected的 constexpr 支持不完整,涉及模板推导时可能编译失败
真正落地时,多数项目会先封装一层薄胶水(比如加 map、unwrap_or),而不是直接裸用标准版。








