第 18.20 節

std::filesystem

0瀏覽次數0訪問次數--跳出率--平均停留

What problem does this section solve?

Prior to C++17, performing file and directory operations—such as traversing, creating, deleting, or checking for existence—relied on platform-specific APIs (e.g., POSIX's <dirent.h>, Windows' FindFirstFile), preventing code from being cross-platform.

std::filesystem provides cross-platform file system operations, where a single codebase works across Linux, macOS, and Windows.

What is this feature?

std::filesystem is a file system library introduced in C++17 that provides features like path handling, file/directory operations, traversal, and permission queries. The core class is std::filesystem::path.

C++ standard version

C++17

Required header files

#include <filesystem>

Basic Syntax

namespace fs = std::filesystem;

fs::path p = "/home/user/data.txt";  // 路径对象

// 检查文件
fs::exists(p);          // 是否存在
fs::is_regular_file(p); // 是否是普通文件
fs::is_directory(p);    // 是否是目录
fs::file_size(p);       // 文件大小

// 操作
fs::create_directory(p);     // 创建目录
fs::copy(src, dst);           // 拷贝文件
fs::remove(p);                // 删除文件/目录
fs::rename(old, new);         // 重命名

// 遍历
for (const auto& entry : fs::directory_iterator(p)) { ... }      // 非递归
for (const auto& entry : fs::recursive_directory_iterator(p)) {} // 递归

Common operations

OperationExplanation
fs::exists(p)路径是否存在
fs::is_directory(p)Is this the table of contents?
fs::is_regular_file(p)Is this a regular file?
fs::create_directory(p)Create Directory
fs::create_directories(p)Recursively create directories (similar to mkdir -p)
fs::copy(src, dst)Copy files
fs::remove(p)Delete file or empty directory
fs::remove_all(p)Recursive delete (similar to rm -rf)
fs::rename(old, new)Rename/Move
p.filename()Get file name
p.extension()Get the extension name.
p.parent_path()Get parent directory
p / "subdir"Path concatenation (using the / operator)

Example code

Example 1: Check file/directory existence and size information

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main()
{
    // 用当前目录做演示
    fs::path current = fs::current_path();
    std::cout << "Current path: " << current << "\n";
    std::cout << "Exists: " << std::boolalpha << fs::exists(current) << "\n";
    std::cout << "Is directory: " << fs::is_directory(current) << "\n";

    // 获取路径的各部分
    std::cout << "Filename: " << current.filename() << "\n";
    std::cout << "Parent path: " << current.parent_path() << "\n";
    std::cout << "Root path: " << current.root_path() << "\n";

    return 0;
}

Running results (paths may vary by machine):

Current path: "/home/user/MySource/my-blog"
Exists: true
Is directory: true
Filename: "my-blog"
Parent path: "/home/user/MySource"
Root path: "/"

Example 2: Building on Example 1, iterate through all files in the directory

#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main()
{
    fs::path dir = fs::current_path();

    std::cout << "Contents of " << dir << ":\n";
    std::cout << "--------------------------------\n";

    // 遍历当前目录(不递归子目录)
    for (const auto& entry : fs::directory_iterator(dir))
    {
        std::string type = entry.is_directory() ? "[DIR] " : "[FILE]";
        std::cout << type << " " << entry.path().filename().string() << "\n";
    }

    return 0;
}

Running Results (vary depending on the directory contents):

Contents of "/home/user/MySource/my-blog":
--------------------------------
[DIR]  .git
[DIR]  app
[DIR]  content
[FILE] nuxt.config.ts
[FILE] package.json
[FILE] README.md
...

Example 3: Filtering files with specific extensions based on Example 2

#include <iostream>
#include <filesystem>
#include <vector>

namespace fs = std::filesystem;

// 查找指定目录下所有 .md 文件
std::vector<fs::path> find_md_files(const fs::path& dir)
{
    std::vector<fs::path> result;
    for (const auto& entry : fs::recursive_directory_iterator(dir))
    {
        if (entry.is_regular_file() && entry.path().extension() == ".md")
        {
            result.push_back(entry.path());
        }
    }
    return result;
}

int main()
{
    fs::path dir = fs::current_path() / "content";

    if (!fs::exists(dir))
    {
        std::cout << "Directory not found: " << dir << "\n";
        return 1;
    }

    auto md_files = find_md_files(dir);

    std::cout << "Found " << md_files.size() << " .md files:\n";
    for (const auto& f : md_files)
    {
        std::cout << "  " << f.filename().string() << "\n";
    }

    return 0;
}

Run results (depending on file content):

Found 15 .md files:
  index.md
  ch1-前言.md
  ch18-现代C++.md
  ...

Example 4: Building on Example 3, create directories, files, and copy them.

#include <iostream>
#include <filesystem>
#include <fstream>

namespace fs = std::filesystem;

int main()
{
    fs::path test_dir = fs::temp_directory_path() / "cpp_test_demo";

    // 创建目录(如果已存在也不报错)
    if (fs::create_directory(test_dir))
    {
        std::cout << "Created: " << test_dir << "\n";
    }
    else
    {
        std::cout << "Already exists: " << test_dir << "\n";
    }

    // 创建一个文件
    fs::path file_path = test_dir / "hello.txt";
    {
        std::ofstream out(file_path);
        out << "Hello, filesystem!\n";
    }
    std::cout << "Created file: " << file_path << "\n";
    std::cout << "File size: " << fs::file_size(file_path) << " bytes\n";

    // 拷贝文件
    fs::path copy_path = test_dir / "hello_copy.txt";
    fs::copy(file_path, copy_path);
    std::cout << "Copied to: " << copy_path << "\n";

    // 检查两个文件都存在
    std::cout << "hello.txt exists: " << fs::exists(file_path) << "\n";
    std::cout << "hello_copy.txt exists: " << fs::exists(copy_path) << "\n";

    // 清理:删除测试目录
    fs::remove_all(test_dir);
    std::cout << "Cleaned up: " << test_dir << "\n";

    return 0;
}

Results

Created: "/tmp/cpp_test_demo"
Created file: "/tmp/cpp_test_demo/hello.txt"
File size: 21 bytes
Copied to: "/tmp/cpp_test_demo/hello_copy.txt"
hello.txt exists: true
hello_copy.txt exists: true
Cleaned up: "/tmp/cpp_test_demo"

runtime results

See the "running results" for each example above.

Key syntax explanation in the example

|Here is the translation of the provided Simplified Chinese Markdown fragment into natural American English, following all specified rules.


ExampleDiscusses whatNewly emerged syntaxWhy write it this wayPrecautions
Example 1Path information lookupfs::current_path()exists()filename()Get the current path and its components.All path operations are cross-platform.
Example 2Browse directoryfs::directory_iteratorIterate over all entries in the current directoryOnly traverses the first level and does not recursively enter subdirectories.
Example 3recursive traversal and filteringfs::recursive_directory_iteratorextension()Recursively find files with a specific suffix

When you need to recursively locate all files that end with a particular suffix in a directory and its subdirectories, here are the most common methods by platform:

Command Line (Bash/Linux/macOS):
Use the find command.
Example: Find all .txt files starting from the current directory.

find . -type f -name "*.txt"

Command Line (PowerShell/Windows):
Use Get-ChildItem (alias gci or dir) with the -Recurse flag.
Example: Find all .log files recursively.

Get-ChildItem -Path . -Recurse -Filter *.log

Programming (Python):
Use os.walk() or the pathlib module (recommended for clarity).
Example using pathlib:

from pathlib import Path

suffix = ".py"
for p in Path('.').rglob(f'*{suffix}'):
    print(p)

Programming (Java):
Use Files.walk().
Example:

import java.nio.file.*;
import java.util.stream.Stream;

String suffix = ".java";
try (Stream<Path> paths = Files.walk(Paths.get("."))) {
    paths.filter(Files::isRegularFile)
         .filter(p -> p.toString().endsWith(suffix))
         .forEach(System.out::println);
} catch (Exception e) { e.printStackTrace(); }

All these methods will traverse directories recursively to match your target file suffix.|Recursive traversal can be slow, so watch out for directory depth.| |Example 4|create, copy, delete|create_directory()copy()remove_all()|Common file operations, cross-platform|remove_all is similar to rm -rf, use with caution|

Exception-based version vs error_code version

Many filesystem functions have two usage methods:

auto size = fs::file_size(path);      // 失败时抛异常

std::error_code ec;
auto size = fs::file_size(path, ec);  // 失败时不抛异常,错误写入 ec
writing methodAdvantagesSuitable scenarios
Throw Exception VersionShort code automatically interrupts on failureThe file should exist; failure is an exception.
error_code versionDo not throw exceptions, convenient for continuous processing.Scanning a large number of files, with uncertain permissions, allowing skipping of failed items

Example 5: Skipping Failed Items with error_code During Directory Scanning

#include <filesystem>
#include <iostream>
#include <system_error>

// filesystem 用来跨平台处理路径、文件和目录。
namespace fs = std::filesystem;

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    fs::path dir = fs::current_path();

    for (const auto& entry : fs::directory_iterator(dir))
    {
        std::error_code ec;
        auto size = fs::file_size(entry.path(), ec);

        if (ec)
        {
            std::cout << "[skip] " << entry.path().filename().string()
                      << " reason: " << ec.message() << "\n";
            continue;
        }

        std::cout << entry.path().filename().string()
                  << " size = " << size << "\n";
    }

    return 0;
}

Running Results (vary depending on the directory contents):

[skip] content reason: Is a directory
package.json size = 1234
README.md size = 2048

When scanning directories, encountering directories with insufficient permissions or abnormal symbolic links is quite common. Using error_code allows the program to skip problematic items and continue running, rather than exiting upon the first failure.

Common Errors

Error 1: Forgetting to check exists before operating on it

std::cout << fs::file_size("/nonexistent");  // ❌ 抛异常!

The correct approach: perform a exists() check first, or use try-catch.

Error 2: Confusion between create_directory and create_directories

fs::create_directory("a/b/c");  // ❌ 如果 a/b 不存在则失败!

Correct approach: fs::create_directories("a/b/c"); will create recursively.

Error 3: Comparing path with ==

if (path1 == path2)  // 只是字符串比较!

The paths /home/user and /home/user/ might not be equal, use fs::equivalent(p1, p2) for normalized comparison.

Error 4: -lstdc++fs is required when linking (legacy compiler)

In GCC 8 and earlier, it was necessary to link the stdc++fs library: g++ -std=c++17 file.cpp -lstdc++fs. GCC 9+ and Clang do not require this.

使用建议

  • 明确目标:在开始前确定您的具体需求,以便选择最合适的工具或教程。
  • 充分利用资源:参考官方文档、教程和博客,这些资料能帮助您快速上手并解决问题。
  • 实践应用:通过动手操作项目或编写代码来巩固学习成果,提升实际操作能力。
  • 问题解决:遇到困难时,查阅参考资料或寻求社区支持,逐步培养独立解决问题的能力。
  • 分享经验:完成项目后,可以撰写文章或博客分享心得,帮助其他学习者。

如果需要针对特定领域(如单片机、机器人或环境搭建)的进一步建议,请提供更多信息,我将为您细化内容。

  1. Use namespace fs = std::filesystem; to simplify code: This abbreviation is almost a community standard.
  2. Concatenating paths with the / operator: auto p = base / "subdir" / "file.txt";, concise and cross-platform.
  3. Traversing large directories with directory_iterator:Avoid loading all files at once.
  4. Exception safety in file operations: Many filesystem functions throw fs::filesystem_error when they fail.
  5. C++17 filesystem is generally sufficient: C++20/23 added some convenience functions but the core remains unchanged.
  6. Consider error_code overloads when scanning large files: Prevent a single error condition from interrupting the entire scan.

Summary

  • std::filesystem provides cross-platform file system operations.
  • fs::path is a path class that supports joining and splitting (/, filename()/extension()/parent_path()).
  • exists() check for existence, create_directory() create directories, copy()/remove() and operate files.
  • Traverse the directory, recursive_directory_iterator recursively traverse.
  • Check exists() before operation, paying attention to exception handling.
音乐页