C++17 中的 std::filesystem: 文件系统操作的新手指南

在 C++17 标准中,<filesystem> 库被正式纳入标准库,提供了一套统一、跨平台的文件系统操作接口。相比传统的 dirent.hwindows.h 等平台特定的 API,std::filesystem 极大地简化了路径操作、文件和目录的创建、遍历、属性查询以及移动、复制等功能。本文将从基本概念、常用类型和函数入手,帮助你快速掌握 C++17 的文件系统操作。

1. 基本概念与命名空间

#include <filesystem>
namespace fs = std::filesystem;
  • 路径(path)fs::path 表示一个文件系统路径,可以是相对路径、绝对路径、UNC 路径等。其内部使用 std::stringstd::wstring 存储,提供了大量成员函数用于解析、拼接、获取文件名、扩展名等。
  • 文件系统句柄:C++ 标准并未直接定义句柄,但提供了 fs::file_status(文件状态信息)和 fs::directory_entry(目录条目)等结构体,用于描述文件属性。

2. 路径操作

fs::path p1("/home/user/documents");
fs::path p2("report.txt");

// 拼接路径
fs::path full = p1 / p2;            // /home/user/documents/report.txt

// 访问路径元素
std::cout << "Filename: " << full.filename() << '\n';          // report.txt
std::cout << "Stem: " << full.stem() << '\n';                  // report
std::cout << "Extension: " << full.extension() << '\n';        // .txt

// 转为字符串
std::string str = full.string();

路径类支持运算符重载,使用 /+= 等可以方便地进行路径拼接,兼容 Windows 与 POSIX 语义。

3. 文件和目录属性

fs::file_status status = fs::status(full); // 查询文件状态
bool exists = fs::exists(full);            // 是否存在
bool is_regular = fs::is_regular_file(full); // 是否普通文件
bool is_dir = fs::is_directory(full);      // 是否目录

// 获取文件大小
auto file_size = fs::file_size(full);

注意fs::status 可能抛出异常,如果你只想检查是否存在,使用 fs::exists 更安全。

4. 创建、移动、删除

// 创建目录(递归)
fs::create_directories("/tmp/a/b/c");

// 创建单个文件夹
fs::create_directory("/tmp/new_folder");

// 复制文件
fs::copy(full, "/tmp/backup/report.txt", fs::copy_options::overwrite_existing);

// 移动文件
fs::rename(full, "/tmp/new_location/report.txt");

// 删除文件
fs::remove("/tmp/old_file.txt");

// 删除目录(若为空)
fs::remove("/tmp/empty_dir");

// 删除目录(递归)
fs::remove_all("/tmp/old_folder");

fs::copy_options 枚举提供了多种复制策略,例如 skip_existingoverwrite_existingrecursive 等,能满足不同需求。

5. 遍历目录

C++17 提供了两种主要遍历方式:

5.1 基础遍历

for (const auto& entry : fs::directory_iterator("/home/user")) {
    std::cout << entry.path() << '\n';
}
  • directory_iterator:非递归遍历,只列出指定目录下的直接子项。
  • 异常:若遇到不可读目录,迭代器会抛出 std::filesystem::filesystem_error

5.2 递归遍历

for (const auto& entry : fs::recursive_directory_iterator("/home/user")) {
    std::cout << entry.path() << '\n';
}
  • recursive_directory_iterator:递归遍历整个目录树。
  • 过滤器:可以在构造函数中传入 fs::directory_options::skip_permission_denied,忽略权限不足的文件夹,避免异常中断。

6. 文件权限和时间戳

// 设置权限
fs::permissions(full, fs::perms::owner_read | fs::perms::owner_write, fs::perm_options::replace);

// 获取权限
auto perms = fs::status(full).permissions();
std::cout << std::bitset<16>(static_cast<unsigned>(perms)) << '\n';

// 访问时间戳
auto ftime = fs::last_write_time(full);
std::time_t cftime = decltype(ftime)::clock::to_time_t(ftime);
std::cout << std::asctime(std::localtime(&cftime));

文件权限的位掩码与 POSIX 或 Windows 的权限概念保持一致,兼容多平台。

7. 常见错误与调试

  • 路径非法:Windows 中路径包含冒号 :、问号 ? 等非法字符,fs::path 在构造时不会检查,但在访问文件系统时会抛异常。
  • 异常处理:所有 std::filesystem 函数会在错误时抛 std::filesystem::filesystem_error,因此建议使用 try/catch 捕获或在代码中使用 fs::exists 预检查。
  • 跨平台差异fs::path::native() 返回平台原生字符串,注意编码问题(Windows 上使用 UTF-16)。

8. 代码实例:复制项目目录

下面给出一个完整示例,演示如何递归复制一个目录到另一个位置,同时忽略权限不足的子目录。

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

void copy_directory(const fs::path& src, const fs::path& dst) {
    if (!fs::exists(src) || !fs::is_directory(src))
        throw std::runtime_error("Source directory does not exist or is not a directory.");

    fs::create_directories(dst);

    for (const auto& entry : fs::recursive_directory_iterator(src,
        fs::directory_options::skip_permission_denied)) {
        try {
            const auto& path = entry.path();
            auto relative = fs::relative(path, src);
            auto target = dst / relative;
            if (entry.is_directory())
                fs::create_directories(target);
            else if (entry.is_regular_file())
                fs::copy_file(path, target, fs::copy_options::overwrite_existing);
        } catch (const fs::filesystem_error& ex) {
            std::cerr << "Failed to copy " << entry.path() << ": " << ex.what() << '\n';
        }
    }
}

int main() {
    try {
        fs::path source = "/home/user/project";
        fs::path destination = "/tmp/project_backup";
        copy_directory(source, destination);
        std::cout << "复制完成!\n";
    } catch (const std::exception& ex) {
        std::cerr << "错误: " << ex.what() << '\n';
    }
}

运行后,/tmp/project_backup 将包含与原目录结构相同的所有文件。

9. 小结

  • std::filesystem 提供了跨平台的文件系统操作接口,减少了平台特定代码。
  • 路径、属性、权限、时间戳、遍历等功能都以对象化方式暴露,语法简洁。
  • 异常机制帮助捕获错误,但也需要在高性能场景下注意性能开销。

通过掌握上述基本使用方法,你可以在 C++17 项目中轻松完成文件系统相关的需求,提升代码可维护性与可移植性。祝你编码愉快!

发表评论