std::expected<T, E> 是 C++23 提供的零成本错误处理机制,强制调用方显式处理成功与失败分支,适用于预期会失败且需响应的场景(如文件操作、网络请求),优于 errno + 返回值、std::optional 或异常滥用。

std::expected 替代 errno + 返回值的典型写法
传统 C++ 文件操作常靠 std::ifstream::fail() 或手动检查 errno,逻辑分散、易漏判。C++23 的 std::expected<T, E> 把成功值和错误原因绑在一起,强制调用方处理两种分支——不是“可选”,而是“必须考虑失败”。
实操建议:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 用
std::expected<std::string, std::error_code>表示读取文件内容的结果,而不是返回空字符串或抛异常 - 错误类型优先选
std::error_code(含 category 和 value),别用裸 int 或 string——它能跨平台映射系统错误(如EACCES→std::errc::permission_denied) - 构造
std::unexpected时直接传std::error_code{errno, std::generic_category()},别手写错误码数字
auto read_file(const std::filesystem::path& p) -> std::expected<std::string, std::error_code> {
std::ifstream f{p};
if (!f.is_open()) {
return std::unexpected(std::error_code{errno, std::generic_category()});
}
std::string content{std::istreambuf_iterator{f}, {}};
return content;
}
std::expected::and_then 处理连续 IO 操作链
多个文件操作串联(如“读配置→解析→加载资源”)时,用 and_then 可避免嵌套 if,且天然短路:任一环节失败,后续不执行。
常见错误现象:手动写 if (ok) { ... } else { return err; } 套三层,漏掉某次 return 导致未定义行为。
立即学习“C++免费学习笔记(深入)”;
实操建议:
-
and_then接收一个返回std::expected的 lambda,不能返回裸值或 void - lambda 参数是前一步的 success 值,类型必须严格匹配——比如上一步返回
std::expected<int, E>,lambda 形参就得是int,不是int&(除非你明确想移动) - 不要在
and_then里 throw;若需异常语义,用value_or_throw()显式转换
auto load_config_and_resources(const std::string& path)
-> std::expected<ResourceBundle, std::error_code> {
return read_file(path)
.and_then([](std::string cfg) {
return parse_config(cfg); // returns expected<Config, ec>
})
.and_then([](Config cfg) {
return load_resources(cfg); // returns expected<ResourceBundle, ec>
});
}
与 std::optional、异常处理的边界在哪
std::expected 不是万能替代品。它适合“预期会失败、且调用方必须响应”的场景,比如打开文件、解析 JSON、网络请求。但对“本不该发生”的错误(如内存耗尽、指针解引用空值),仍该用异常或断言。
性能影响:std::expected 是零成本抽象,无动态分配,但比裸 bool + out-param 略重(因要存储错误对象)。不过现代编译器对小错误类型(如 std::error_code)优化很好。
兼容性注意点:
- MSVC 19.35+、GCC 13+、Clang 16+ 才完整支持
std::expected;旧版本需用abseil::StatusOr或自己封装 - 别混用
std::optional<T>和std::expected<T, std::error_code>:前者表示“可能没有值”,后者表示“有明确失败原因”,语义不同 - 函数返回
std::expected时,调用方若只关心成功路径,必须显式调用.value()或.value_or(...),否则编译报错——这是优点,也是约束
std::expected 的错误传播与日志调试
错误信息容易在多层 and_then 中被吞掉。比如底层返回 std::error_code{ENOENT},中间层却转成更模糊的 std::error_code{std::errc::io_error},导致定位困难。
实操建议:
- 错误传递时尽量保留原始
std::error_code,别随意映射;必要时用std::make_error_condition构造新条件,但注明来源 - 调试时直接打印
e.error().message()和e.error().value(),比看堆栈更快;e.error().category().name()能区分是系统错误还是自定义错误 - 日志中记录
e.has_value() ? "success" : "failed: " + e.error().message(),避免漏掉失败分支
复杂点在于:错误类型 E 必须可复制(或可移动),且不能抛异常的析构函数。一旦你自定义错误类,这点极易被忽略——而编译器不会报错,只会在运行时报 undefined behavior。


















