C++17 标准库新增了 std::filesystem 模块,它为文件系统相关操作提供了统一、跨平台的接口。相比传统的 POSIX 或 Windows API,std::filesystem 更加简洁、类型安全,并且能在编译时捕获错误。下面从安装、核心概念、常用操作、异常处理以及性能注意事项等方面,系统介绍如何使用 std::filesystem。
1. 环境准备
- 编译器:g++ 8.1+、clang++ 6.0+、MSVC 2017+ 支持 std::filesystem。
- 标准:在编译时加上
-std=c++17(或更高)并链接-lstdc++fs(部分实现需要):g++ -std=c++17 -lstdc++fs main.cpp -o main
2. 基本命名空间与类型
#include <filesystem>
namespace fs = std::filesystem;
核心类型:
fs::path:路径对象,支持字符串、字符类型等构造。fs::directory_entry:目录项,用于遍历目录。fs::directory_iterator/recursive_directory_iterator:迭代器。fs::file_status/fs::space_info:文件状态与磁盘空间信息。
3. 常用操作
| 功能 | 示例代码 | 说明 |
|---|---|---|
| 判断路径存在 | if (fs::exists(p)) { … } |
检查文件或目录是否存在 |
| 创建目录 | fs::create_directories(p); |
递归创建多级目录 |
| 删除文件/目录 | fs::remove(p); / fs::remove_all(p); |
删除单个或递归删除 |
| 重命名 | fs::rename(old_p, new_p); |
更改文件或目录名称 |
| 读取文件大小 | auto sz = fs::file_size(p); |
以字节为单位返回 |
| 复制文件 | fs::copy(source, dest, fs::copy_options::overwrite_existing); |
可设置复制选项 |
| 遍历目录 | for (const auto &entry : fs::directory_iterator(dir)) { … } |
非递归 |
| 递归遍历 | for (const auto &entry : fs::recursive_directory_iterator(dir)) { … } |
递归 |
4. 路径操作
fs::path p = "/usr/local/bin";
std::cout << "文件名: " << p.filename() << '\n'; // bin
std::cout << "父目录: " << p.parent_path() << '\n'; // /usr/local
std::cout << "是否为绝对路径: " << p.is_absolute() << '\n';
使用 operator/ 拼接路径更加直观:
fs::path log_dir = "/var/log";
fs::path log_file = log_dir / "app.log";
5. 处理符号链接
fs::path link = "/tmp/link";
if (fs::is_symlink(link)) {
fs::path target = fs::read_symlink(link); // 读取指向的目标
}
6. 异常处理
std::filesystem 的大多数操作会抛出 std::filesystem::filesystem_error。你可以通过捕获该异常来获取错误码与路径信息。
try {
fs::create_directory("/root/forbidden");
} catch (const fs::filesystem_error &e) {
std::cerr << "Error: " << e.what() << '\n';
std::cerr << "Error code: " << e.code() << '\n';
}
如果你不想抛出异常,可以使用带 std::error_code& 参数的函数。例如:
std::error_code ec;
fs::create_directory("/root/forbidden", ec);
if (ec) {
std::cout << "创建失败: " << ec.message() << '\n';
}
7. 性能与线程安全
- 批量操作:尽量一次性完成相关操作,避免频繁系统调用。
- 缓存:若在多线程环境下频繁查询同一目录,可自行缓存
directory_entry对象。 - 递归删除:
fs::remove_all在大目录下可能会出现性能瓶颈;可考虑使用boost::filesystem::recursive_directory_iterator的手动实现进行分批删除。
8. 示例:复制一个目录树
下面给出一个完整的例子,将源目录复制到目标目录,并保留符号链接。
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void copy_dir(const fs::path &src, const fs::path &dst) {
if (!fs::exists(src) || !fs::is_directory(src)) {
throw std::runtime_error("源目录不存在或不是目录");
}
fs::create_directories(dst);
for (const auto &entry : fs::recursive_directory_iterator(src)) {
const auto &path_in_src = entry.path();
auto relative_path = fs::relative(path_in_src, src);
auto path_in_dst = dst / relative_path;
try {
if (entry.is_symlink()) {
auto target = fs::read_symlink(path_in_src);
fs::create_symlink(target, path_in_dst);
} else if (entry.is_directory()) {
fs::create_directory(path_in_dst);
} else if (entry.is_regular_file()) {
fs::copy_file(path_in_src, path_in_dst, fs::copy_options::overwrite_existing);
}
} catch (const fs::filesystem_error &e) {
std::cerr << "复制失败: " << e.what() << '\n';
}
}
}
int main() {
try {
copy_dir("/home/user/project", "/home/user/backup/project");
std::cout << "复制完成\n";
} catch (const std::exception &e) {
std::cerr << "错误: " << e.what() << '\n';
return 1;
}
return 0;
}
9. 小结
std::filesystem让文件系统操作更安全、可读且跨平台。- 通过异常或错误码两种方式灵活处理错误。
- 结合
fs::path的强大路径处理能力,减少手动字符串拼接错误。 - 在性能要求高的场景,注意批量操作与缓存。
掌握上述基本用法后,你可以在 C++17 或更高版本的项目中无缝使用 std::filesystem,既提升代码质量,又兼顾跨平台的兼容性。