C++ directory_iterator | Traverse folders with std::filesystem

C++ directory_iterator | Traverse folders with std::filesystem

이 글의 핵심

Practical guide to iterating directories: recursion, skip_permission_denied, symlink policies, visiting sets for cycles, and when to batch subtrees for parallelism.

What is directory_iterator?

Iterate directory entries (C++17).

#include <filesystem>

namespace fs = std::filesystem;

for (const auto& entry : fs::directory_iterator(".")) {
    std::cout << entry.path() << std::endl;
}

Recursive traversal

for (const auto& entry : fs::recursive_directory_iterator(".")) {
    std::cout << entry.path() << std::endl;
}

directory_iterator vs recursive_directory_iterator

directory_iteratorrecursive_directory_iterator
ScopeImmediate children onlyFull subtree
DefaultDoes not descendDescends automatically
Optionsdirectory_optionsSame family + disable_recursion_pending
Typical useList one levelFull-tree search, size totals

There is no standard max-depth flag. Implement depth limits with manual recursion/queues, or call disable_recursion_pending() to skip entering specific directories (e.g. build).

for (fs::recursive_directory_iterator it(root), end; it != end; ++it) {
    if (it->is_directory() && it->path().filename() == "build") {
        it.disable_recursion_pending();
    }
}

Practical examples

Example 1: List files

void listFiles(const fs::path& dir) {
    for (const auto& entry : fs::directory_iterator(dir)) {
        if (entry.is_regular_file()) {
            std::cout << entry.path().filename() << std::endl;
        }
    }
}

Example 2: Find extension

std::vector<fs::path> findFiles(const fs::path& dir, 
                                 const std::string& ext) {
    std::vector<fs::path> result;
    
    for (const auto& entry : fs::recursive_directory_iterator(dir)) {
        if (entry.is_regular_file() && 
            entry.path().extension() == ext) {
            result.push_back(entry.path());
        }
    }
    
    return result;
}

Example 3: Directory size

uintmax_t calculateDirSize(const fs::path& dir) {
    uintmax_t size = 0;
    
    for (const auto& entry : fs::recursive_directory_iterator(dir)) {
        if (entry.is_regular_file()) {
            size += entry.file_size();
        }
    }
    
    return size;
}

Example 4: Filter by size

void filterFiles(const fs::path& dir) {
    for (const auto& entry : fs::directory_iterator(dir)) {
        if (entry.is_regular_file()) {
            auto size = entry.file_size();
            
            if (size > 1024 * 1024) {
                std::cout << entry.path() << ": " 
                          << size << " bytes" << std::endl;
            }
        }
    }
}

Filtering patterns

The standard has no glob/regex filter—combine:

  • Extension: path::extension(), path::stem().
  • Type: is_regular_file(), is_directory().
  • Exclude dirs: skip paths containing .git, node_modules, etc.
  • Regex: apply to path::string() if needed—mind Windows encodings.

With C++20 you can chain std::ranges adapters for cleaner filters.

Iterator options

fs::directory_iterator it(dir);

fs::directory_iterator it(dir, 
    fs::directory_options::follow_directory_symlink);

fs::directory_iterator it(dir,
    fs::directory_options::skip_permission_denied);
  • Directory symlinks: recursion may follow them—unexpectedly large trees or cycles.
  • follow_directory_symlink: explicitly follow; still no automatic cycle detection—track visited canonical paths on sensitive workloads.
  • Link vs target: is_symlink(), symlink_status vs status distinguish link metadata from final type.
  • Hard links: the same inode through multiple paths can double-count size—deduplicate if required.

Advanced: search by name

#include <filesystem>
#include <string>
#include <vector>

namespace fs = std::filesystem;

std::vector<fs::path> find_by_name(const fs::path& root, std::string_view key) {
    std::vector<fs::path> out;
    auto opts = fs::directory_options::skip_permission_denied;
    std::error_code ec;
    for (const auto& e : fs::recursive_directory_iterator(root, opts, ec)) {
        if (ec) break;
        if (!e.is_regular_file()) continue;
        auto fn = e.path().filename().string();
        if (fn.find(key) != std::string::npos)
            out.push_back(e.path());
    }
    return out;
}

Advanced: tree byte count

std::uintmax_t tree_bytes(const fs::path& p, std::error_code& ec) {
    std::uintmax_t total = 0;
    auto opts = fs::directory_options::skip_permission_denied;
    for (const auto& e : fs::recursive_directory_iterator(p, opts, ec)) {
        if (ec) break;
        if (!e.is_regular_file()) continue;
        std::error_code fe;
        auto sz = fs::file_size(e.path(), fe);
        if (!fe) total += sz;
    }
    return total;
}

error_code overloads

std::error_code ec;
for (const auto& entry : fs::directory_iterator("maybe_missing", ec)) {
    if (ec) {
        std::cerr << ec.message() << '\n';
        break;
    }
    std::cout << entry.path() << '\n';
}

Combine with skip_permission_denied so permission errors on single entries do not kill the walk. Still check ec around file_size/status on critical paths.

Performance

  1. Avoid unnecessary deep recursion when you only need shallow scans.
  2. Cache entry.path() in a local variable inside hot loops.
  3. Syscalls: directory_entry may cache metadata—do not assume, but avoid redundant queries.
  4. Parallelism: split by subtrees + thread pools—watch disk contention.
  5. Network drives: high latency—batch and tune cache policy.

Common pitfalls

Pitfall 1: Exceptions vs error_code

Use skip_permission_denied and error_code when you cannot afford exceptions on every unreadable folder.

Document traversal policy; add a visited set for risky trees.

Pitfall 3: Performance

Prefer entry.file_size() when available instead of extra fs::file_size(path) calls.

Pitfall 4: Modify while iterating

Undefined behavior if you delete entries as you iterate—collect paths first, then delete.

FAQ

Q1: What is directory_iterator?

A: C++17 facility to enumerate directory entries.

Q2: Recursive walks?

A: recursive_directory_iterator.

Q3: Errors?

A: Exceptions or error_code overloads; skip_permission_denied helps.

Q4: Performance?

A: Reuse path, minimize syscalls, consider parallel subtree walks.

Q5: Modify during iteration?

A: Avoid—collect then mutate.

Q6: Learning resources?

A: C++17 – The Complete Guide, C++ Primer, cppreference — directory_iterator.


  • C++ File Status
  • C++ File Operations
  • C++ Filesystem library

Practical tips

Debugging

  • Fix warnings first.
  • Reproduce on a small directory tree.

Performance

  • Profile real trees.

Code review

  • Follow conventions.

Production checklist

Before coding

  • Correct traversal strategy?
  • Symlink policy documented?

While coding

  • Errors handled?
  • Edge cases (permissions, races)?

At review

  • Clear intent?
  • Tests on sample trees?

Keywords

C++, directory_iterator, recursive_directory_iterator, filesystem, C++17, error_code


  • C++ File Operations |
  • C++ File Status |
  • C++ Filesystem quick reference |
  • C++ Filesystem overview |
  • C++ path |