在 C++17 标准中,std::filesystem 被正式纳入标准库,它为文件和目录的操作提供了统一、跨平台的接口。相比传统的 POSIX 调用或 Windows API,std::filesystem 极大简化了代码复杂度,同时提高了可读性和可维护性。下面通过几个典型场景来探讨其核心特性和常见坑。
1. 基础概念
- 路径(path):
std::filesystem::path表示文件系统中的路径,支持字符串拼接、路径分隔符自动处理。 - 文件系统对象:文件、目录、符号链接等都可以通过
path来引用。 - 操作:遍历、创建、移动、删除、权限检查等。
2. 常用 API 示例
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
// 创建目录
fs::create_directories("logs/2026/01");
// 判断文件是否存在
if (fs::exists("config.json")) {
std::cout << "配置文件已存在\n";
}
// 拷贝文件
fs::copy("data.txt", "backup/data.txt", fs::copy_options::overwrite_existing);
// 删除文件或目录
fs::remove_all("temp");
// 遍历目录
for (const auto& entry : fs::recursive_directory_iterator("src")) {
std::cout << entry.path() << '\n';
}
3. 路径解析与字符串化
fs::path p = "/var/log/../tmp/./file.log";
std::cout << "Canonical: " << fs::canonical(p) << '\n'; // 解析成绝对路径
std::cout << "Parent: " << p.parent_path() << '\n'; // 上级目录
std::cout << "Extension: " << p.extension() << '\n'; // 文件扩展名
4. 错误处理
std::filesystem 的大多数函数会抛出 std::filesystem::filesystem_error。可以使用 try/catch 或者检查返回值(如 remove 的布尔返回)。
try {
fs::remove("nonexistent.file");
} catch (const fs::filesystem_error& e) {
std::cerr << e.what() << '\n';
}
5. 线程安全
- 读取文件系统状态(如
exists,is_directory)是线程安全的。 - 写操作(如
create_directory,remove)在多线程环境下不保证原子性;需要自行同步或使用原子操作。
6. 性能注意
recursive_directory_iterator在大目录结构中会产生大量系统调用,可能导致性能下降。必要时可使用directory_options::follow_directory_symlink或手动过滤。canonical需要解析符号链接,代价较大,尽量避免在循环中频繁调用。
7. 常见坑
| 场景 | 错误做法 | 正确做法 |
|---|---|---|
| 路径拼接 | std::string dir = "/usr"; dir += "/bin"; |
fs::path dir = "/usr"; dir /= "bin"; |
| 复制文件 | fs::copy(file, dst); |
指定 copy_options::overwrite_existing,否则会抛异常 |
| 删除目录 | fs::remove(dir); |
需要 fs::remove_all 或先判断是否为空 |
| 遍历时删除 | 在迭代器中直接删除会导致迭代器失效 | 先收集待删除路径,再统一删除 |
8. 小结
std::filesystem 让 C++ 代码与文件系统交互更直观、可读。掌握其核心 API、错误处理机制以及线程安全细节,能够大幅提升项目的可靠性与可维护性。若你正在从传统方式迁移,建议先在小型实验项目中逐步替换文件操作代码,验证性能与兼容性后再全面推广。