在C++17标准中引入了<filesystem>库,提供了一套强大的跨平台文件和目录操作接口。相比传统的POSIX或Windows API,std::filesystem 的优势在于语义清晰、异常安全、易于使用。下面从安装、常见操作、异常处理、性能优化以及常见陷阱几个方面,系统阐述如何在实际项目中高效使用 std::filesystem。
1. 环境准备
| 编译器 | 版本 | 支持情况 |
|---|---|---|
| GCC | 8.1+ | 完全支持 |
| Clang | 7+ | 完全支持 |
| MSVC | 2017+ | 完全支持 |
| Apple Clang | 10+ | 完全支持 |
使用 -std=c++17(或更高)编译,并在链接时加入 -lstdc++fs(GCC 8以下版本需要手动链接)或直接使用 GCC 9+、Clang 10+、MSVC 2017+ 时不需额外链接。
g++ -std=c++17 -Wall -Wextra main.cpp -o app
2. 基本语法与使用示例
2.1 包含头文件
#include <filesystem>
namespace fs = std::filesystem;
2.2 创建目录
fs::create_directories("logs/2026/02");
create_directories会一次性创建所有缺失的父目录。若目录已存在,不会抛异常。
2.3 检查文件/目录是否存在
if (fs::exists("config.ini")) {
std::cout << "配置文件已存在\n";
}
2.4 读取目录内容
for (const auto &entry : fs::directory_iterator("logs")) {
std::cout << entry.path() << " " << fs::file_size(entry) << " bytes\n";
}
2.5 移动/复制/删除
fs::rename("temp.txt", "archive/2026/temp.txt"); // 重命名或移动
fs::copy_file("config.ini", "backup/config.ini", fs::copy_options::overwrite_existing); // 复制
fs::remove("old.log"); // 删除文件
fs::remove_all("temp_dir"); // 递归删除目录
2.6 路径操作
fs::path p = "/usr/local/bin";
p /= "gcc"; // 追加子路径
p = p.parent_path(); // 父目录
p.replace_extension(".exe"); // 改扩展名
3. 异常与错误处理
std::filesystem 的大多数函数在失败时会抛 std::filesystem::filesystem_error。建议:
try {
fs::create_directory("logs");
} catch (const fs::filesystem_error& e) {
std::cerr << "创建目录失败: " << e.what() << '\n';
}
filesystem_error里包含错误码errno,可通过e.code()获取std::error_code,进一步分析原因。
如果你不想抛异常,可以使用 std::error_code 版本的函数:
std::error_code ec;
fs::create_directory("logs", ec);
if (ec) {
std::cerr << "错误: " << ec.message() << '\n';
}
4. 性能优化建议
| 场景 | 优化方法 |
|---|---|
| 大量遍历目录 | 使用 fs::recursive_directory_iterator 并设置 fs::directory_options::follow_directory_symlink 仅在必要时跟随符号链接 |
| 频繁检查文件是否存在 | 在批量操作前一次性读取目录结构,缓存 path 到 file_size 的映射 |
| 并发访问 | std::filesystem 本身不是线程安全的,建议为每个线程使用独立的 error_code 或在外部使用互斥锁 |
5. 常见陷阱
| 陷阱 | 说明 | 解决方案 |
|---|---|---|
| 路径分隔符硬编码 | 在 Windows 用 \、Linux 用 / |
使用 fs::path 自动处理,或使用 fs::path::preferred_separator |
| 对象生命周期导致悬空 | directory_iterator 在迭代完成后对象被销毁,导致后续使用失效 |
只在循环内部使用 path,不要在循环外存储迭代器 |
| 递归删除空目录失误 | remove_all 删除不止一个层级 |
若只想删除指定层级,先检查 is_empty 后再 remove |
| 符号链接导致无限递归 | 递归遍历时不加 follow_directory_symlink |
明确设置 directory_options |
6. 小案例:备份工具
#include <filesystem>
#include <iostream>
#include <chrono>
#include <iomanip>
namespace fs = std::filesystem;
void backup(const fs::path& src, const fs::path& dst) {
std::error_code ec;
if (!fs::exists(src, ec)) {
std::cerr << "源文件不存在\n";
return;
}
// 创建时间戳目录
auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::ostringstream oss;
oss << std::put_time(std::localtime(&t), "%Y%m%d%H%M%S");
fs::path target_dir = dst / oss.str();
fs::create_directories(target_dir, ec);
if (ec) {
std::cerr << "创建备份目录失败: " << ec.message() << '\n';
return;
}
fs::copy_file(src, target_dir / src.filename(), fs::copy_options::overwrite_existing, ec);
if (ec) {
std::cerr << "文件复制失败: " << ec.message() << '\n';
} else {
std::cout << "备份完成: " << target_dir / src.filename() << '\n';
}
}
int main() {
backup("config.ini", "backup");
return 0;
}
该示例展示了如何利用
std::filesystem在运行时创建时间戳备份目录,并安全复制文件。
7. 结语
std::filesystem 在 C++17 中成为了跨平台文件操作的标准工具。它将繁琐的 API 用简洁的面向对象方式呈现,降低了代码复杂度,并提供了良好的异常安全保障。只要在项目中正确配置编译环境并注意上述陷阱,使用 std::filesystem 将使文件与目录管理工作更加高效、可靠。祝你编码愉快!