C++之文件系统库filesystem

1 阅读8分钟

std::filesystem 库是 C++17 引入的一项重要特性,为文件和目录操作提供了统一、跨平台的现代 C++ 接口。

  • 路径处理
  • 文件/目录操作
  • 遍历目录
  • 文件属性查询
  • 文件复制/删除/重命名
  • 跨平台文件系统抽象

核心组件

std::filesystem核心组件一览

组件描述
path表示文件系统路径,封装了路径字符串和平台差异
directory_entry表示一个目录项(文件或子目录),缓存了路径和文件状态信息
file_status存储文件类型和权限信息
space_info存储文件系统的容量、剩余空间和可用空间信息
filesystem_error异常类,当文件系统操作失败时抛出
directory_iterator遍历目录内容的迭代器(非递归)
recursive_directory_iterator递归遍历目录及其所有子目录的迭代器

cplus-filesystem.png

意义

解决传统 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:\aC:``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

字符串转换

  1. 在 Windows 上避免string()generic_string(),应使用 u8string()wstring():因 ANSI 代码页通常不支持完整 Unicode。
  2. 异常安全性:遇到无效编码或无法转换的字符时,可能抛出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_iteratorrecursive_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_allremove_all 递归删除目录及其内容,极具危险性
重命名rename(old, new_p)new_p已存在且为文件,则会被覆盖
空间space(p)文件系统磁盘空间space_info(包含 capacity, free, available)

remove:

  • 如果 p 是一个常规文件,直接删除,返回 true
  • 如果 p 是一个空目录(不包含任何子条目),删除该目录,返回 true
  • 如果 p 是非空目录,不会删除(通常返回 false,且 error_code 会被设置为非零,表示目录非空或操作不允许)。
  • 如果 p 是符号链接,删除符号链接本身,不删除其目标。这和 Unix unlink 行为一致(对于目录符号链接,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,而非字符串