在C++17标准中,std::filesystem库被引入,以统一和简化跨平台文件和目录操作。该库封装了文件路径、文件属性、文件移动、复制、删除等常见任务,使得代码更加安全、可读和可移植。下面将从概念介绍、主要功能、常见用法以及实践技巧四个部分,系统地讲解如何在项目中高效地使用std::filesystem。
1. 概念与目标
1.1 统一接口
在C++之前,文件操作往往依赖于各操作系统的API(Windows的WinAPI、Linux的POSIX等),导致代码缺乏可移植性。std::filesystem为文件系统提供了统一的C++接口,内部实现会根据目标平台自动选择对应的底层实现。
1.2 路径对象(std::filesystem::path)
路径被封装为std::filesystem::path对象,支持字符编码、分隔符自动识别、路径拼接、路径解析等功能。相比传统的字符串操作,路径对象可以避免手动处理斜杠/反斜杠、路径结尾空格等细节错误。
1.3 操作集合
标准库提供了以下几大类功能:
- 查询:
exists,is_regular_file,is_directory,file_size,last_write_time等 - 修改:
create_directory,create_directories,remove,remove_all,rename,copy等 - 迭代:
directory_iterator,recursive_directory_iterator等 - 属性:
permissions,last_write_time的读写
2. 主要API演示
2.1 路径拼接
#include <filesystem>
namespace fs = std::filesystem;
fs::path base = "/usr/local";
fs::path file = "bin/myapp";
fs::path full = base / file; // /usr/local/bin/myapp
2.2 判断文件是否存在
fs::path p = "/tmp/test.txt";
if (fs::exists(p)) {
std::cout << "文件存在\n";
}
2.3 创建目录
fs::path dir = "/tmp/data/logs";
fs::create_directories(dir); // 若目录已存在,不报错
2.4 文件复制与移动
fs::path src = "/tmp/input.dat";
fs::path dst = "/tmp/backup/input.dat";
fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
fs::rename(src, src.parent_path() / "input_old.dat");
2.5 递归遍历目录
for (const auto& entry : fs::recursive_directory_iterator("/var/log")) {
if (entry.is_regular_file()) {
std::cout << entry.path() << '\n';
}
}
2.6 文件大小与修改时间
auto sz = fs::file_size("/var/log/syslog");
auto t = fs::last_write_time("/var/log/syslog");
std::cout << "大小: " << sz << " 字节\n";
3. 异常处理
std::filesystem大多数函数在出现错误时会抛出std::filesystem::filesystem_error。可以使用try/catch捕获并获取详细信息:
try {
fs::remove_all("/tmp/old_logs");
} catch (const fs::filesystem_error& e) {
std::cerr << "删除失败: " << e.what() << '\n';
std::cerr << "路径: " << e.path1() << " 错误码: " << e.code() << '\n';
}
如果你不想抛异常,可以在调用时使用std::error_code:
std::error_code ec;
fs::remove_all("/tmp/old_logs", ec);
if (ec) {
std::cerr << "错误: " << ec.message() << '\n';
}
4. 性能与跨平台注意
- 路径对象与字符串:
fs::path内部存储使用std::u8string或std::wstring,在Windows上使用宽字符,Linux使用UTF-8。直接使用字符串可能导致编码问题,最好通过fs::path统一处理。 - 递归迭代:
recursive_directory_iterator在深层目录或符号链接较多时可能产生大量系统调用,影响性能。可通过depth或skip_permission_denied参数进行优化。 - 文件权限:Windows与POSIX的权限模型不同,
fs::permissions在两者上表现略有差异,需根据目标平台调试。
5. 实战案例:快速构建日志文件归档工具
下面给出一个完整的示例,演示如何使用std::filesystem快速实现一个日志归档工具:
#include <filesystem>
#include <chrono>
#include <iomanip>
#include <iostream>
#include <sstream>
namespace fs = std::filesystem;
// 把日志文件移动到归档目录,按日期归档
void archiveLogs(const fs::path& sourceDir, const fs::path& archiveRoot) {
for (const auto& entry : fs::directory_iterator(sourceDir)) {
if (!entry.is_regular_file()) continue;
// 只处理 .log 后缀
if (entry.path().extension() != ".log") continue;
// 获取文件最后修改时间
auto ftime = fs::last_write_time(entry);
auto sysTime = decltype(ftime)::clock::to_time_t(ftime);
std::tm tm{};
#if defined(_WIN32) || defined(_WIN64)
localtime_s(&tm, &sysTime);
#else
localtime_r(&sysTime, &tm);
#endif
std::ostringstream oss;
oss << std::put_time(&tm, "%Y-%m-%d");
fs::path destDir = archiveRoot / oss.str();
fs::create_directories(destDir);
fs::rename(entry.path(), destDir / entry.path().filename());
std::cout << "已归档: " << entry.path() << " -> " << destDir / entry.path().filename() << '\n';
}
}
int main() {
fs::path logs = "/var/log/myapp";
fs::path archive = "/var/log/myapp/archive";
try {
archiveLogs(logs, archive);
} catch (const fs::filesystem_error& e) {
std::cerr << "归档错误: " << e.what() << '\n';
}
return 0;
}
该程序完成了:
- 遍历日志目录;
- 过滤出
.log文件; - 按文件最后修改日期创建归档子目录;
- 将文件移动到对应目录。
6. 小结
std::filesystem为C++提供了跨平台、类型安全、易于维护的文件系统操作接口;- 通过
fs::path对象处理路径、fs::create_directories等函数构建文件树、fs::copy_file/rename实现移动、递归迭代处理大规模文件; - 异常机制和
std::error_code提供了错误处理的两种灵活方式; - 在实际项目中,尽量使用
std::filesystem替代手写的系统调用或第三方库,提高可维护性与可移植性。
掌握这些核心概念与用法后,你就能在任何需要文件系统交互的C++项目中,以简洁而安全的方式完成文件与目录的增删改查。