std::expected 替代 bool + out-param,封装成功值与失败原因,支持链式调用;需满足 e 可拷贝且无抛构造,推荐 std::error_code 而非 std::string,跨平台错误提取应封装,and_then 用于返回 expected 的操作,transform 仅做纯转换,禁止在回调中抛异常。

std::expected 替代 bool + out-param 的典型场景
文件打开失败时,传统做法常返回 bool 并通过引用参数传入错误码或异常信息,既难组合又易忽略错误分支。C++23 的 std::expected<t e></t> 直接把「成功值」和「失败原因」封装进一个对象,天然支持链式调用与模式匹配。
比如读取配置文件并解析 JSON,原来要写三层嵌套检查:fstream::is_open()、ifstream::good()、json::parse() 异常捕获;现在可统一为 std::expected<json std::error_code></json>,每个步骤返回该类型,用 and_then 串起来。
- 必须包含
<expected></expected>头文件(GCC 13.2+ / Clang 17+ / MSVC 19.38+ 支持) -
E类型需满足std::copy_constructible和std::is_nothrow_copy_constructible_v(std::error_code满足,std::string不满足——别直接用std::string作错误类型) - 若需携带上下文信息(如文件路径),应包装成自定义错误类型,而非拼接字符串
std::expected<:ifstream std::error_code> 的安全构造方式
不能直接对 std::ifstream 调用 .open() 后检查 .fail() 再构造 expected——这会丢失底层系统错误码。正确做法是使用 POSIX open() 或 Windows CreateFile() 获取原始错误,再转为 std::error_code;但更实际的是复用 std::filesystem 的错误传播机制。
示例:安全打开只读文件
std::expected<:ifstream std::error_code> open_readonly(std::string_view path) {
std::error_code ec;
if (!std::filesystem::is_regular_file(path, ec)) {
return std::unexpected(ec);
}
std::ifstream f{path.data(), std::ios::in | std::ios::binary};
if (!f.is_open()) {
return std::unexpected(std::make_error_code(
static_cast<:errc>(errno))); // Linux/macOS
}
return f;
}</:errc></:ifstream>
-
std::ifstream构造函数不抛异常,但可能静默失败(如权限不足),必须显式检查is_open() - 不要用
std::system_category()包装 errno——std::make_error_code已适配 - Windows 下需用
GetLastError()替代errno,建议封装跨平台错误提取函数
链式处理中 and_then 与 transform 的关键区别
and_then 用于返回另一个 std::expected 的操作(如“打开后读取”),而 transform 仅对成功值做纯转换(如“读取后解析为 int”)。混用会导致编译失败或逻辑断裂。
常见错误:在 transform 里做可能失败的操作(如 json::parse),结果返回 json 而非 std::expected<json ...></json>,一旦解析失败就崩溃。
- 正确链路:
open_readonly(...) >> read_all_bytes >> parse_json(全部返回expected) -
>>是and_then的 operator 重载,语义清晰;避免手写嵌套if (auto e = ...) - 若某步无需失败传播(如 UTF-8 校验),可用
transform,但校验失败时应返回std::unexpected(...)而非抛异常
与异常处理共存时的资源清理陷阱
std::expected 本身不触发栈展开,所以 RAII 对象(如临时 std::vector<char></char> 缓冲区)会在作用域结束时自动析构;但若在 and_then 回调里抛异常,且外层未捕获,会导致资源泄漏——因为 expected 的设计假设你全程用它传递错误。
- 禁止在
and_then或transform的 lambda 中 throw:要么返回std::unexpected,要么用std::unreachable()标记不可能分支 - 若必须调用可能抛异常的第三方库(如某些 JSON 解析器),先用
try/catch捕获并转为std::unexpected - 移动语义要注意:
std::expected<t></t>移动后原对象处于有效但未指定状态,别再访问其value()或error()
最易被忽略的是跨模块 ABI 兼容性:std::expected 是字节级布局敏感类型,不同编译器或标准库版本间不能直接传递二进制对象——动态库接口仍建议用 C 风格错误码 + out-param。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











