std::expected 不自动传播错误,必须显式检查每个返回值;否则忽略 has_value() 直接调用 value() 会抛 std::bad_expected_access。

std::expected 在多层函数调用中怎么传递错误而不崩
直接结论:std::expected 本身不自动传播错误,必须显式检查每个返回值;否则一旦忽略 std::expected<t e></t> 的 has_value() 状态,后续解包(如调用 value())会抛 std::bad_expected_access。
典型场景是“读配置 → 解析 JSON → 校验字段 → 初始化服务”,每步都可能失败且错误类型不同(std::filesystem::filesystem_error、nlohmann::json::parse_error、自定义 ValidationError)。这时不能靠异常穿透——你得让错误在类型系统里“走完全程”。
- 每层函数返回
std::expected<t errortype></t>,**不要**用void或裸T - 调用方必须用
if (auto res = f()) { use(res.value()); } else { handle(res.error()); },别图省事写f().value() - 错误类型建议用
std::variant统一收口(比如using AppError = std::variant<configerror parseerror validationerror>;</configerror>),避免模板实例爆炸
如何把不同层级的错误统一转成一个 std::expected 返回值
关键不是“转换”,而是“构造”。std::expected 的 error 成员类型在模板参数里就固定了,所以函数签名必须提前约定好最终的错误类型。中间步骤的原始错误要主动映射过去。
例如:JSON 解析失败时,nlohmann::json::parse_error 不是你的业务错误,得包装成 ParseError{.msg = e.what(), .offset = e.byte} 再塞进 std::unexpected(ParseError{...})。
立即学习“C++免费学习笔记(深入)”;
- 用
std::make_unexpected(e)构造错误分支,别手写std::expected<t>{std::unexpect, e}</t> - 如果某步本应成功但意外失败(比如
std::stoi抛异常),用try/catch捕获后转为预期错误,**不要**让异常逃出返回std::expected的函数 - 避免在 lambda 或临时对象里隐式构造
std::unexpected,某些编译器(如 GCC 13)对移动语义处理不一致,易触发复制而非移动
std::expected 链式调用时怎么优雅回退(类似 Rust 的 ? 操作符)
C++23 没有内置 ?,但可以用 and_then + lambda 模拟。注意:它只在有值时调用后续逻辑,否则直接短路返回当前错误——这才是“优雅回退”的本质。
示例:从文件读字符串 → 转 JSON → 提取字段 "timeout" → 转整数:
auto load_timeout = read_file("config.json")
.and_then([](std::string s) {
return parse_json(s).and_then([](nlohmann::json j) {
return extract_timeout(j).and_then([](std::string s) {
return str_to_int(s); // 返回 expected<int, ConvError>
});
});
});
-
and_then接收一个返回std::expected的函数,**不能**传返回裸值的函数 - 如果某步返回
std::unexpected,整个链立刻终止,不再执行后续and_then,错误原样透出 - 想在某步做日志或清理?用
transform(不影响控制流)或显式if分支,别强塞进and_then的 lambda 里
和异常比,std::expected 回退慢吗?哪些地方容易拖慢性能
绝大多数情况下不慢,但有两个隐蔽开销点:一是 std::expected 对象本身有大小(通常等于 max(sizeof(T), sizeof(E)) + 1 字节),二是 and_then 链会生成多个嵌套 lambda,某些优化级别下可能抑制内联。
- 别把大对象(如
std::vector<uint8_t></uint8_t>)直接塞进std::expected的T,改用std::unique_ptr或移动语义保证零拷贝 - 错误类型
E尽量轻量:用enum class+ 字符串字面量,而不是带堆分配的std::string - GCC/Clang 在
-O2下对and_then链内联效果不错,但 MSVC 2022 17.8 前对嵌套 lambda 支持较弱,实测延迟高 5–10%,上线前务必压测
最常被忽略的是:错误路径的构造成本。比如每次解析失败都 new 一个详细错误对象,这比抛异常还重——std::expected 的优势在于确定性,不是无代价。


















