C++17文件系统库(std::filesystem)使用指南

在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::u8stringstd::wstring,在Windows上使用宽字符,Linux使用UTF-8。直接使用字符串可能导致编码问题,最好通过fs::path统一处理。
  • 递归迭代recursive_directory_iterator在深层目录或符号链接较多时可能产生大量系统调用,影响性能。可通过depthskip_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;
}

该程序完成了:

  1. 遍历日志目录;
  2. 过滤出.log文件;
  3. 按文件最后修改日期创建归档子目录;
  4. 将文件移动到对应目录。

6. 小结

  • std::filesystem为C++提供了跨平台、类型安全、易于维护的文件系统操作接口;
  • 通过fs::path对象处理路径、fs::create_directories等函数构建文件树、fs::copy_file/rename实现移动、递归迭代处理大规模文件;
  • 异常机制和std::error_code提供了错误处理的两种灵活方式;
  • 在实际项目中,尽量使用std::filesystem替代手写的系统调用或第三方库,提高可维护性与可移植性。

掌握这些核心概念与用法后,你就能在任何需要文件系统交互的C++项目中,以简洁而安全的方式完成文件与目录的增删改查。

发表评论