std::expected不能直接捕获errno,需手动读取并映射为自定义错误类型(如enum class file_error),配合RAII封装(如unique_fd)确保资源安全,且链式调用要求统一错误类型。

std::expected 不能直接捕获 errno,必须手动映射
标准库的 std::expected 本身不感知系统错误码(比如 errno),它只负责承载成功值或自定义错误类型。文件打开失败时,fopen 或 open() 返回空指针或 -1,但不会自动把 errno 塞进 std::expected ——你得自己读、判断、封装。
常见错误现象:std::expected<FILE*, int> 看起来合理,但若直接返回 nullptr 而不保存 errno,就丢失了具体原因(是 ENOENT 还是 EACCES?)。
- 调用
open()后立即读取errno,且仅在返回 -1 时读 ——errno在成功调用后可能被覆盖,不可靠 - 推荐封装一个枚举类(如
enum class file_error),显式映射errno值,避免裸int语义模糊 - 不要用
std::error_code直接替代 —— 它虽兼容errno,但和std::expected组合时需额外构造,不如自定义枚举清晰
用 std::expected<int, file_error> 封装 open() 更安全
open() 返回文件描述符(非负整数)或 -1,适合用 std::expected<int, file_error> 表达:成功路径传 fd,失败路径传明确错误分类。
示例关键逻辑:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
std::expected<int, file_error> safe_open(const char* path, int flags) {
int fd = ::open(path, flags);
if (fd == -1) {
switch (errno) {
case ENOENT: return std::unexpected(file_error::not_found);
case EACCES: return std::unexpected(file_error::permission_denied);
case EISDIR: return std::unexpected(file_error::is_directory);
default: return std::unexpected(file_error::unknown);
}
}
return fd;
}- 必须检查
fd == -1,而不是fd < 0——open()只在错误时返回 -1,其他负值非法 - 不要忽略
EINTR:生产环境应重试,但重试逻辑不宜塞进safe_open—— 它职责应是“一次尝试 + 明确结果” - 若需支持
O_CLOEXEC等标志,确保调用方传入合法组合,safe_open不做校验,留给系统调用兜底
std::expected 与 RAII 文件句柄需配合 unique_fd 类型
单纯返回 int fd 有资源泄漏风险:std::expected 的值可能被临时变量持有,忘记 close;或异常中途跳出导致未释放。
正确做法是让 std::expected 携带一个 RAII 封装类型(如 unique_fd),而非裸 fd:
struct unique_fd {
int fd_ = -1;
explicit unique_fd(int fd) : fd_(fd) {}
~unique_fd() { if (fd_ != -1) ::close(fd_); }
unique_fd(const unique_fd&) = delete;
unique_fd& operator=(const unique_fd&) = delete;
unique_fd(unique_fd&& o) : fd_(o.fd_) { o.fd_ = -1; }
};-
unique_fd移动语义必须置空源对象的fd_,否则析构时双重 close 可能触发EBADF - 返回
std::expected<unique_fd, file_error>后,调用方可直接用.value()获取已保活的句柄,无需担心生命周期 - 不要在
unique_fd构造函数里做dup()或权限检查 —— 那属于业务逻辑,应由上层决定
链式调用中 error() 值易被隐式转换干扰
当多个 std::expected 操作串联(如 open → fstat → mmap),每个步骤都可能失败。若错误类型不一致(比如前一步用 file_error,下一步用 io_error),and_then 会编译失败。
常见错误现象:auto res = safe_open(...).and_then(safe_fstat) 报错 “no matching function for call to and_then”,实际是 safe_fstat 返回类型与前序 error_type 不匹配。
- 统一错误类型是前提 —— 全流程用同一个
enum class(如io_error),不同操作映射各自 errno 到该枚举 - 避免在 lambda 中直接 throw:C++23 的
std::expected不支持异常传播,and_then内部抛异常会导致 terminate - 调试时别依赖
.error()输出 —— 枚举值默认无字符串化,需手写to_string(file_error)辅助日志
最麻烦的其实是 errno 的线程安全性:多线程下 errno 是 per-thread 的,但如果你在 signal handler 里调用了 open(),而 signal handler 又没屏蔽该信号,errno 可能被覆盖 —— 这种边界情况往往被忽略,但一旦出问题极难复现。

















