std::filesystem 库是 C++17 引入的一项重要特性,为文件和目录操作提供了统一、跨平台的现代 C++ 接口。
- 路径处理
- 文件/目录操作
- 遍历目录
- 文件属性查询
- 文件复制/删除/重命名
- 跨平台文件系统抽象
核心组件
std::filesystem核心组件一览
| 组件 | 描述 |
|---|---|
path | 表示文件系统路径,封装了路径字符串和平台差异 |
directory_entry | 表示一个目录项(文件或子目录),缓存了路径和文件状态信息 |
file_status | 存储文件类型和权限信息 |
space_info | 存储文件系统的容量、剩余空间和可用空间信息 |
filesystem_error | 异常类,当文件系统操作失败时抛出 |
directory_iterator | 遍历目录内容的迭代器(非递归) |
recursive_directory_iterator | 递归遍历目录及其所有子目录的迭代器 |
意义
解决传统 C/C++ 文件系统操作混乱、不统一的问题:
- Linux / Windows API 不统一
- UTF-8 / 宽字符混乱
- 路径分隔符不同
- 容易写出不安全字符串逻辑
- 错误处理复杂
| 能力 | 传统方案 |
|---|---|
| 文件存在检测 | access() / stat() |
| 创建目录 | mkdir() |
| 遍历目录 | dirent.h |
| Windows兼容 | WinAPI |
| 路径拼接 | 手写字符串 |
核心类型与方法
fs::path
path 类是整个库的基石,负责路径的存储、解析和操作;但并不保证路径在磁盘上真实存在。
| 分类 | 常用接口 | 说明 |
|---|---|---|
| 分解 | root_name() root_directory() root_path() | 根名称(路径的命名前缀),Windows 独有 根目录 根路径 |
relative_path() parent_path() filename() | 获取相对路径 父路径 完整文件名 | |
stem() extension() | 文件名(不含后缀) 扩展名(含.) | |
| 组合 | operator/=, append() | 追加:自动添加平台相关的分隔符 |
operator+=, concat() | 拼接:纯字符串连接,不添加分隔符 | |
replace_extension() replace_filename() | 替换后缀 替换文件名 | |
| 转换 | string() u8string() wstring() | 窄字符编码的字符串 转换为 UTF-8 字符串 宽字符表示 |
generic_string() | 转换为通用格式(使用/作为分隔符),编码同 string() | |
| 查询 | empty() has_extension() is_absolute() | 路径是否为空字符串 有没有文件后缀(有没有.xxx) 是不是绝对路径,root_path () 非空 |
root 相关
root_name():判断 Windows 盘符 / 网络路径)root_directory():判断路径是否是顶层根目录root_path():快速获取完整根路径
| 场景 | root_name() | root_directory() | root_path() |
|---|---|---|---|
Windows 盘符 C:\a | C: | `` | C: |
Windows 网络 \s\a | \s | `` | \s |
Linux 绝对 /a | 空 | / | / |
相对路径 a/b | 空 | 空 | 空 |
路径拆分
- relative_path() = 路径 − 根路径
- parent_path() = 路径 − 最后一段
- filename() = 路径最后一段
| 方法 | 含义 | 作用 |
|---|---|---|
| relative_path() | 相对根的路径 | 去掉root_path()之后剩下的全部内容 |
| parent_path() | 父路径 | 去掉最后一段(文件名 / 最后一级目录)剩下的路径 |
| filename() | 文件名 | 路径的最后一段(文件或目录名) |
1. Windows 路径
路径:"C:\Users\Admin\file.txt"
root_path() = C:\
relative_path() = Users\Admin\file.txt
parent_path() = C:\Users\Admin
filename() = file.txt
2. Linux 路径
路径:"/home/admin/file.txt"
root_path() = /
relative_path() = home/admin/file.txt
parent_path() = /home/admin
filename() = file.txt
3. 目录路径(无文件)
路径:"/home/data/docs/"
root_path() = /
relative_path() = home/data/docs/
parent_path() = /home/data
filename() = docs (最后一级目录名)
4. 相对路径(无根)
路径:"projects/hello/src"
root_path() = 空
relative_path() = projects/hello/src
parent_path() = projects/hello
filename() = src
字符串转换
- 在 Windows 上避免
string()和generic_string(),应使用u8string()或wstring():因 ANSI 代码页通常不支持完整 Unicode。 - 异常安全性:遇到无效编码或无法转换的字符时,可能抛出
std::filesystem::filesystem_error。
| 方法 | 分隔符风格 | 字符编码(平台相关) | 跨平台推荐 |
|---|---|---|---|
string() | 原生 | POSIX: UTF-8 Windows: ANSI 代码页 | ❌ 避免 |
u8string() | 原生 | 始终 UTF-8 | ✅ 首选 |
wstring() | 原生 | Windows: UTF-16 Linux: UTF-32 | 按需使用 |
generic_string() | 正斜杠 / | 同 string() | ❌ 避免 |
generic_u8string() | 正斜杠 / | 始终 UTF-8 | ✅ 备选 |
fs::directory_entry
directory_entry 缓存了文件状态(如大小、时间、类型),避免在遍历时频繁进行系统调用。
directory_iterator: 非递归遍历当前目录。recursive_directory_iterator: 深度优先递归遍历,可使用depth()获取当前深度(起始目录深度为0),使用disable_recursion_pending()跳过对某个子目录的遍历
| 接口 | 说明 |
|---|---|
exists() | 检查该项是否存在 |
is_regular_file() is_directory() | 是不是普通文件 是不是文件夹/目录 |
file_size() | 获取文件大小 |
last_write_time() | 获取最后修改时间 |
refresh() | 手动更新缓存的属性 |
fs::recursive_directory_iterator it("/home/user/project");
for (; it != end; ++it) {
if (it->path().filename() == "build" && it->is_directory()) {
it.disable_recursion_pending(); // 不进入 build 内部
std::cout << "[skipped] " << it->path() << '\n';
continue;
}
std::cout << it->path() << '\n';
}
directory_options
directory_options 是一个枚举类,用于控制 directory_iterator 和 recursive_directory_iterator 在遍历目录时的行为
enum class directory_options {
none = 0,
follow_directory_symlink = 1,
skip_permission_denied = 2,
// C++20 新增
directories_only = 4 // 仅遍历目录条目(非标准扩展有的实现提供)
};
- follow_directory_symlink: 当递归迭代器遇到一个指向目录的符号链接时,会跟踪链接,并进入链接指向的真实目录进行递归遍历。
- skip_permission_denied: 遇到因权限不足无法访问的目录时,不抛出异常,而是跳过该目录
文件操作
对操作系统底层 API 的封装,通常提供两个版本:一个抛出异常,另一个接受 std::error_code。
文件与目录操作
| 功能 | 函数接口 | 关键注意点 |
|---|---|---|
| 创建 | create_directory create_directories | 父目录必须存在 自动创建多级父目录 |
| 拷贝 | copy copy_file | 复制文件/复制整个文件夹 精确复制单个文件 |
| 删除 | remove remove_all | remove_all 递归删除目录及其内容,极具危险性 |
| 重命名 | rename(old, new_p) | 若 new_p已存在且为文件,则会被覆盖 |
| 空间 | space(p) | 文件系统磁盘空间space_info(包含 capacity, free, available) |
remove:
- 如果
p是一个常规文件,直接删除,返回true。 - 如果
p是一个空目录(不包含任何子条目),删除该目录,返回true。 - 如果
p是非空目录,不会删除(通常返回false,且error_code会被设置为非零,表示目录非空或操作不允许)。 - 如果
p是符号链接,删除符号链接本身,不删除其目标。这和 Unixunlink行为一致(对于目录符号链接,remove会删除链接,而不是目标目录)。 - 如果
p不存在,返回false(且不会报告错误,除非通过error_code参数检查)。
remove_all:递归删除路径 p 及其所有内容(类似 rm -rf)
- 如果
p是一个文件,删除它,返回1。 - 如果
p是一个目录,删除该目录以及所有子条目(文件、子目录、符号链接等),返回删除的总条目数(包括p本身以及递归删除的所有文件和目录)。 - 如果
p不存在,返回0(不抛出异常) - 符号链接:删除链接本身,不递归进入目标。
- 如果某个文件或目录无法删除(无权限),会停止并抛出异常。
属性查询与修改
| 功能 | 函数接口 | 返回值/说明 |
|---|---|---|
| 状态 | status(p),symlink_status(p) | 返回 file_status,记录文件类型和权限 |
| 大小 | file_size(p) | 仅适用于常规文件 |
| 时间 | last_write_time(p) | 返回 file_time_type(通常是系统时钟) |
| 权限 | permissions(p, prms, opts) | 修改文件或目录的访问权限 |
| 绝对路径 | absolute(p) , canonical(p) | canonical 会解析路径中的 . 和 .. 并要求路径必须存在 |
permissions 参数
prms:权限位(fs::perms 枚举)
| 权限 | 说明 | 八进制 |
|---|---|---|
owner_read | 所有者可读 | 0400 |
owner_write | 所有者可写 | 0200 |
owner_exec | 所有者可执行 | 0100 |
group_read | 组可读 | 0040 |
group_write | 组可写 | 0020 |
group_exec | 组可执行 | 0010 |
others_read | 其他用户可读 | 0004 |
others_write | 其他用户可写 | 0002 |
others_exec | 其他用户可执行 | 0001 |
all | 所有权限 | 0777 |
opts:操作选项(fs::perm_options 枚举)
replace(默认):用prms完全替换原有权限。add:在现有权限基础上追加prms(按位或)。remove:从现有权限中移除prms(按位与非)。nofollow:不跟随符号链接,直接修改链接文件本身。
谓词函数
| 函数 | 核心功能 | 路径不存在 | 说明 |
|---|---|---|---|
exists(p) | 检查路径是否存在 | 返回 false | |
is_empty(p) | 检查是否为空 | 抛出异常 / 返回ec | 目录:无条目则为真; 文件:大小为 0 则为真 |
is_regular_file(p) | 是否为常规文件 | 返回 false | 排除目录、设备文件、管道等 |
is_directory(p) | 是否为目录 | 返回 false | |
is_symlink(p) | 是否为符号链接 | 返回 false | 检查链接本身,不检查目标 |
status_known(s) | 判断文件状态是否有效 | 检查 file_status 是否包含合法、可读取的文件信息 | |
equivalent(p1, p2) | 两路径是否指向同一实体 | 抛出异常 | 比较的是文件系统的inode/ID,而非字符串 |