不能。std::expected 不自动捕获 open() 权限错误,需手动检查返回值、立即读取 errno 并构造 std::error_code;封装时须用 ::open、内联函数、默认 mode=0,并用 e == std::errc::permission_denied 判断权限错。

std::expected 能不能直接捕获 open() 权限错误?
不能。std::expected 本身不自动捕获系统调用错误,它只是个容器——你得自己把 open() 的返回值和 errno 封装进去。C++23 的 std::expected 没有 I/O 语义,不会拦截 EACCES 或 EPERM 这类 errno;它只管你塞进去什么。
常见错误现象:写 std::expected<int std::error_code> fd = open("foo.txt", O_RDONLY);</int> —— 编译失败,因为 open() 返回 int,不返回 std::expected,更不自动转 std::error_code。
- 必须显式检查
open()返回值:小于 0 才代表失败 - 失败时需手动构造
std::error_code(errno, std::generic_category()) -
errno是全局变量,必须在open()后立刻读,中间调任何函数都可能被覆盖
如何封装 open() 成返回 std::expected 的安全函数?
核心是把“检查返回值 + 提取 errno + 构造 error”这三步收进一个内联函数,避免每次手写重复逻辑。注意:O_RDONLY 等 flag 不影响权限判断逻辑,但会影响 open() 是否触发 EACCES(比如对目录用 O_WRONLY 可能过,但 O_RDONLY 失败)。
inline std::expected<int, std::error_code> safe_open(
const char* path, int flags, mode_t mode = 0) {
int fd = ::open(path, flags, mode);
if (fd == -1) {
return std::unexpected(std::error_code(errno, std::generic_category()));
}
return fd;
}
- 函数名用
safe_open比open_or_error更贴近实际搜索习惯 - 必须用
::open防止意外匹配到重载或自定义open函数 - mode 参数默认为 0,避免创建文件时误传未初始化值
- 返回
std::expected<int std::error_code></int>,不是std::expected<int int></int>—— 后者无法和标准库错误处理互操作
std::expected 和 errno 值怎么对应权限错误?
std::error_code 的值本身不等于 errno,而是由 category 决定解释方式。用 std::generic_category() 时,std::error_code(13, std::generic_category()) 就等价于 EACCES,可直接用 == 比较。
立即学习“C++免费学习笔记(深入)”;
常见权限相关 errno 值:
-
EACCES(值 13):路径中某级目录无执行(x)权限,或文件无读权限 -
EPERM(值 1):通常涉及 capability 限制(如容器里 drop CAP_DAC_OVERRIDE),不是普通权限问题 -
EISDIR(值 21):对目录用了O_WRONLY或O_RDWR,也常被误认为权限错
判断时别写 if (e.value() == EACCES),而要用 e == std::errc::permission_denied —— 这才是跨平台、category-aware 的写法。
std::expected 在文件流场景下容易被忽略的坑
很多人想用 std::expected 包裹 std::ifstream 构造,但这是错的:std::ifstream 构造函数不抛异常也不返回状态,只能靠 .is_open() 或 .fail() 判断,且此时 errno 已丢失。
- 不要试图封装
std::ifstream构造为std::expected—— 它不暴露底层 fd 和 errno - 如果必须用流,先用
safe_open拿到 fd,再用fdopen(fd, "r")+std::filebuf绑定,但要注意fdopen不复制 fd,后续关闭责任要理清 - Windows 上
open()不可用,得用_sopen_s或CreateFileW,std::generic_category()仍适用,但 errno 映射需验证
真正难的不是封装,是 errno 的时效性和多线程下 errno 的可靠性——哪怕用了 std::expected,只要中间穿插了其他系统调用,errno 就可能被改写。所以封装函数里,open() 后必须紧跟着读 errno,一步都不能断。



















