C++17中的std::filesystem:文件系统操作的新方式

在 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_iteratorrecursive_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. 性能与安全注意

  1. 异常安全

    • 大多数文件系统操作采用 RAII 设计,错误通过异常抛出。建议使用 try/catch 或者 std::error_code 形式捕获错误,避免程序崩溃。
  2. std::error_code 版本

    • 对于性能敏感或需要错误码而非异常的情况,使用 std::error_code 重载。例如:
    std::error_code ec;
    fs::remove_all("/tmp/temp", ec);
    if (ec) {
        std::cerr << "删除失败: " << ec.message() << '\n';
    }
  3. 跨平台差异

    • Windows 与 POSIX 在符号链接支持、文件权限模型上存在差异。fs::pathpath::filename() 等函数已经做了适配,但在使用符号链接时仍需注意 create_symlink 在 Windows 需要管理员权限。
  4. 大文件与缓冲

    • 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 还将获得更多功能与优化,值得在项目中积极使用。

发表评论