在 C++17 标准正式加入 std::filesystem 前,程序员在进行文件与目录操作时常常需要依赖第三方库(如 Boost.Filesystem)或操作系统特定的 API。随着 std::filesystem 的到来,标准库提供了一个统一、跨平台的文件系统接口,使得文件读取、遍历、复制、移动以及权限管理等操作变得更加直观与安全。本文将介绍 std::filesystem 的核心概念、常用功能以及实际使用中的一些技巧。
1. 核心概念
1.1 path 类型
std::filesystem::path 用于表示文件系统路径。它内部会根据平台自动处理正斜杠与反斜杠,支持字符串拼接、路径分割、尾部斜杠去除等操作。
#include <filesystem>
namespace fs = std::filesystem;
fs::path p1 = "/home/user";
fs::path p2 = "documents";
fs::path full = p1 / p2 / "report.txt"; // /home/user/documents/report.txt
1.2 迭代器
directory_iterator 与 recursive_directory_iterator 分别用于非递归和递归遍历目录。它们遵循 STL 迭代器规范,方便与算法组合。
for (const auto& entry : fs::directory_iterator("/tmp")) {
std::cout << entry.path() << '\n';
}
1.3 操作符与异常
所有文件系统操作函数(如 create_directory, remove_all 等)会在出现错误时抛出 std::filesystem::filesystem_error。可以通过捕获该异常来获取错误码与错误信息。
try {
fs::create_directory("/tmp/newdir");
} catch (const fs::filesystem_error& e) {
std::cerr << e.what() << '\n';
}
2. 常用功能
| 功能 | 典型函数 | 说明 |
|---|---|---|
| 检查路径是否存在 | exists(path) |
返回 bool |
| 判断是否为文件 | is_regular_file(path) |
返回 bool |
| 判断是否为目录 | is_directory(path) |
返回 bool |
| 复制文件/目录 | copy(source, destination, copy_options) |
支持覆盖、递归等 |
| 移动/重命名 | rename(old, new) |
等价于 mv |
| 删除 | remove(path) |
删除单个文件/空目录 |
| 删除所有 | remove_all(path) |
递归删除目录树 |
| 获取文件大小 | file_size(path) |
返回 std::uintmax_t |
| 读写文件权限 | permissions(path, perms, perm_opt) |
设置或查询权限 |
| 获取文件时间 | last_write_time(path) |
返回时间点 |
| 创建硬链接 | create_hard_link(target, new_link) |
|
| 创建符号链接 | create_symlink(target, new_link) |
2.1 复制与移动
fs::copy("/tmp/source.txt",
"/tmp/dest.txt",
fs::copy_options::overwrite_existing); // 覆盖已存在的文件
2.2 递归遍历并过滤
for (auto const& dir_entry : fs::recursive_directory_iterator("/var/log")) {
if (dir_entry.path().extension() == ".log") {
std::cout << dir_entry.path() << '\n';
}
}
2.3 权限处理
fs::permissions("/tmp/secret",
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace); // 只保留所有者读写
3. 性能与安全注意
-
异常安全
- 大多数文件系统操作采用 RAII 设计,错误通过异常抛出。建议使用
try/catch或者std::error_code形式捕获错误,避免程序崩溃。
- 大多数文件系统操作采用 RAII 设计,错误通过异常抛出。建议使用
-
std::error_code版本- 对于性能敏感或需要错误码而非异常的情况,使用
std::error_code重载。例如:
std::error_code ec; fs::remove_all("/tmp/temp", ec); if (ec) { std::cerr << "删除失败: " << ec.message() << '\n'; } - 对于性能敏感或需要错误码而非异常的情况,使用
-
跨平台差异
- Windows 与 POSIX 在符号链接支持、文件权限模型上存在差异。
fs::path与path::filename()等函数已经做了适配,但在使用符号链接时仍需注意create_symlink在 Windows 需要管理员权限。
- Windows 与 POSIX 在符号链接支持、文件权限模型上存在差异。
-
大文件与缓冲
file_size仅返回文件大小,若需逐块读取大型文件,请使用标准 I/O 与std::ifstream,或者使用fs::copy_file进行批量复制以充分利用操作系统级别的高效实现。
4. 实战案例:编写一个简单的备份工具
以下示例演示如何利用 std::filesystem 编写一个命令行备份工具,将指定目录下所有 .txt 文件复制到备份目录,保持相对路径。
#include <filesystem>
#include <iostream>
#include <string>
namespace fs = std::filesystem;
void backup_txt(const fs::path& src, const fs::path& dst) {
for (auto const& dir_entry : fs::recursive_directory_iterator(src)) {
if (dir_entry.is_regular_file() && dir_entry.path().extension() == ".txt") {
fs::path relative = fs::relative(dir_entry.path(), src);
fs::path target = dst / relative;
fs::create_directories(target.parent_path());
fs::copy_file(dir_entry.path(), target, fs::copy_options::overwrite_existing);
std::cout << "已备份: " << dir_entry.path() << " -> " << target << '\n';
}
}
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "用法: backup <源目录> <目标目录>\n";
return 1;
}
fs::path src = argv[1];
fs::path dst = argv[2];
if (!fs::exists(src) || !fs::is_directory(src)) {
std::cerr << "源目录不存在或不是目录。\n";
return 1;
}
try {
backup_txt(src, dst);
} catch (const fs::filesystem_error& e) {
std::cerr << "错误: " << e.what() << '\n';
return 1;
}
return 0;
}
编译示例(GCC 9+):
g++ -std=c++17 -O2 -Wall backup.cpp -o backup
运行:
./backup /home/user/documents /home/user/backup
5. 小结
std::filesystem 为 C++ 程序员提供了一个统一、简洁且功能强大的文件系统操作接口。它摆脱了第三方依赖,提升了代码可移植性与安全性。掌握 path、迭代器、异常处理以及常用函数后,你就能轻松完成文件复制、遍历、权限管理等多种常见任务。随着 C++20 与 C++23 的继续演进,std::filesystem 还将获得更多功能与优化,值得在项目中积极使用。