自从 C++17 标准引入 std::filesystem 模块后,程序员可以在不同平台上以统一、类型安全的方式处理文件和目录。本文将从基础概念、路径处理、文件操作、错误处理以及性能优化几个方面,系统性地梳理 std::filesystem 的使用技巧,并通过实际代码示例帮助你快速上手。
1. 何谓 std::filesystem?
std::filesystem 是 C++17 标准库中新增的一个模块,封装了文件系统相关的 API。其核心类型 std::filesystem::path 与 std::filesystem::directory_entry 通过 STL 风格的容器和迭代器暴露了文件、目录以及属性的访问。相比旧的 POSIX 或 Boost.Filesystem,std::filesystem 具备更简洁的语法、更强的跨平台支持以及更低的错误概率。
#include <filesystem>
namespace fs = std::filesystem;
2. 路径(path)操作
2.1 创建与组合
fs::path p1 = "/usr";
fs::path p2 = "local";
fs::path full = p1 / p2 / "bin"; // /usr/local/bin
使用 / 运算符可以直观地拼接路径,内部会自动处理分隔符,避免手动插入 \ 或 /。
2.2 绝对化与相对化
fs::path rel = "data/file.txt";
fs::path abs = fs::absolute(rel); // 根据当前工作目录生成绝对路径
absolute() 会解析符号链接、., .. 等符号,得到规范化路径。
2.3 访问文件名与扩展名
fs::path file = "/home/user/report.pdf";
std::string stem = file.stem(); // report
std::string ext = file.extension(); // .pdf
std::string name = file.filename(); // report.pdf
3. 文件与目录操作
3.1 判断存在与类型
if (fs::exists(file)) {
if (fs::is_regular_file(file)) { /* 处理普通文件 */ }
else if (fs::is_directory(file)) { /* 处理目录 */ }
}
3.2 创建与删除
fs::create_directories("/tmp/a/b/c"); // 递归创建所有父目录
fs::remove("/tmp/a/b/c/file.txt"); // 删除文件
fs::remove_all("/tmp/a"); // 递归删除目录
3.3 复制与移动
fs::copy(src, dst, fs::copy_options::recursive); // 递归复制
fs::rename(src, dst); // 重命名或移动
3.4 读取与写入
// 写文件
std::ofstream ofs(dst);
ofs << "Hello, filesystem!" << std::endl;
// 读文件
std::ifstream ifs(src);
std::string line;
while (std::getline(ifs, line)) {
std::cout << line << '\n';
}
std::filesystem 并不直接提供读写接口,但结合 iostream 更直观。
3.5 遍历目录
for (const auto& entry : fs::directory_iterator("/var/log")) {
std::cout << entry.path() << '\n';
}
或使用 recursive_directory_iterator 递归遍历。
4. 错误处理
std::filesystem 的大多数函数提供两种调用方式:
- 异常方式:默认抛出
std::filesystem::filesystem_error,可以通过try/catch捕获。 - 错误码方式:传入
std::error_code& ec参数,避免抛异常。
std::error_code ec;
fs::remove("/non/existent/file", ec);
if (ec) {
std::cerr << "删除失败: " << ec.message() << '\n';
}
推荐:在性能敏感或异常抑制的场景下,使用错误码方式;在需要快速调试的情况下,使用异常方式。
5. 性能与实用技巧
| 场景 | 推荐做法 | 说明 |
|---|---|---|
| 批量文件访问 | 预先存储 fs::directory_entry 或 path 列表 |
避免多次 I/O |
| 大文件拷贝 | 使用 fs::copy 的 fs::copy_options::skip_existing |
只复制缺失文件 |
| 遍历深度 | 使用 fs::directory_options::follow_directory_symlink |
是否跟随符号链接 |
| 线程安全 | 对 std::filesystem 只读操作多线程安全,写操作需要外部同步 |
依据需求加锁 |
6. 实战示例:备份日志文件
#include <filesystem>
#include <iostream>
#include <chrono>
#include <iomanip>
namespace fs = std::filesystem;
int main() {
fs::path src_dir = "/var/log";
fs::path dst_dir = "/backup/log";
// 1. 递归复制日志目录
std::error_code ec;
fs::copy(src_dir, dst_dir,
fs::copy_options::recursive |
fs::copy_options::overwrite_existing,
ec);
if (ec) {
std::cerr << "备份失败: " << ec.message() << '\n';
return 1;
}
// 2. 给备份目录添加时间戳
auto now = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(now);
std::tm tm;
#if defined(_WIN32) || defined(_WIN64)
localtime_s(&tm, &t);
#else
localtime_r(&t, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y%m%d_%H%M%S");
fs::path dated_backup = dst_dir / oss.str();
fs::rename(dst_dir, dated_backup, ec);
if (ec) {
std::cerr << "重命名失败: " << ec.message() << '\n';
return 1;
}
std::cout << "日志备份完成: " << dated_backup << '\n';
return 0;
}
此程序将 /var/log 复制到 /backup/log,然后按时间戳重命名。演示了复制、错误码处理、时间格式化与路径组合。
7. 结语
C++17 的 std::filesystem 让文件系统操作从繁琐、易出错的手动字符串拼接变为安全、直观、跨平台的 STL 风格代码。掌握其核心 API 并善用错误码或异常机制,你可以在任何项目中快速、可靠地完成文件、目录的管理任务。下次你遇到文件路径处理需求时,记得先去看看 std::filesystem 能为你提供什么便利吧。