C++ std::filesystem Complete Guide | Cross-Platform Path, Directory, File Operations [#37-1]
이 글의 핵심
Master C++ std::filesystem: cross-platform paths, directory iteration, file operations, permissions, error handling, and production patterns.
Introduction: “Works on Linux, Breaks on Windows”
ifstream Opens on Linux but Not Windows
I wrote code to read config file config/settings.json. It works fine on Linux and macOS, but only on Windows I get “file not found” error.
Cause: Path separators differ. Linux/macOS use /, Windows uses \. Hardcoding "config/settings.json" with / usually works on Windows too, but when combining paths with string concatenation like path + "/" + filename, mixing with config\settings.json causes problems. Bigger issue: confusing executable-relative path vs working directory-relative path.
Solution: C++17 std::filesystem’s path automatically converts to OS-appropriate separator, and operator/ safely combines paths.
// ❌ Bad: string concatenation
std::string configPath = baseDir + "/config/settings.json";
// ✅ Good: std::filesystem::path
#include <filesystem>
namespace fs = std::filesystem;
fs::path configPath = fs::path(baseDir) / "config" / "settings.json";
std::ifstream file(configPath);
After reading this:
- Handle cross-platform paths with
std::filesystem::path. - Traverse directories, copy/delete/move files.
- Check and set file permissions.
- Know common errors and production patterns.
Production experience: This article draws on real problems and fixes from large-scale C++ projects. It includes practical pitfalls and debugging tips that books and reference documentation often omit.
Table of Contents
- Problem Scenarios
- std::filesystem Overview
- Path Operations
- Directory Iteration
- File Operations
- Permissions
- Common Errors and Solutions
- Best Practices
- Production Patterns
- Checklist
- Summary
Analogy
Time, files, logs, JSON are frequently-used wrenches in the toolbox. Standardizing with validated libraries lets the entire team align on same units, same formats.
1. Problem Scenarios
Scenario 1: Program Crashes Because Log Directory Doesn’t Exist
Problem: Trying to write to logs/app.log, but if logs folder doesn’t exist, ofstream open fails.
Solution: Use std::filesystem::create_directories() to create parent directories first.
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
void ensureLogDir(const fs::path& logPath) {
fs::path dir = logPath.parent_path();
if (!dir.empty() && !fs::exists(dir)) {
fs::create_directories(dir);
}
}
int main() {
fs::path logPath = "logs/app.log";
ensureLogDir(logPath);
std::ofstream log(logPath);
log << "Application started\n";
}
Scenario 2: Scan Plugin Folder for .so/.dll Files Only
Problem: Need to load only dynamic libraries from plugins/, but .txt, .md files are mixed in.
Solution: Use directory_iterator to traverse and filter by path.extension().
#include <filesystem>
#include <iostream>
#include <vector>
namespace fs = std::filesystem;
std::vector<fs::path> findPlugins(const fs::path& pluginDir) {
std::vector<fs::path> plugins;
for (const auto& entry : fs::directory_iterator(pluginDir)) {
if (entry.is_regular_file()) {
std::string ext = entry.path().extension().string();
#if defined(_WIN32)
if (ext == ".dll") plugins.push_back(entry.path());
#else
if (ext == ".so") plugins.push_back(entry.path());
#endif
}
}
return plugins;
}
Scenario 3: Uploaded File Permissions Too Open
Problem: Uploaded files have chmod 777-like permissions, creating security risk.
Solution: Use std::filesystem::permissions() to restrict permissions after file creation.
#include <filesystem>
namespace fs = std::filesystem;
void restrictUploadedFile(const fs::path& file) {
// Owner read/write only
fs::permissions(file, fs::perms::owner_read | fs::perms::owner_write);
}
Scenario 4: Temp Files Not Cleaned, Disk Full
Problem: Crashes or exceptions leave temp files undeleted, accumulating. Solution: Wrap temp files in RAII to auto-delete on scope exit.
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
class TempFile {
fs::path path_;
public:
TempFile(const fs::path& base = fs::temp_directory_path()) {
path_ = base / ("tmp_" + std::to_string(std::rand()) + ".tmp");
std::ofstream(path_) << "";
}
~TempFile() {
if (fs::exists(path_)) fs::remove(path_);
}
const fs::path& path() const { return path_; }
};
2. std::filesystem Overview
Architecture
flowchart TB
subgraph path["path: Path Representation"]
P1[String/path combination]
P2[operator/]
P3[extension, stem, filename]
P1 --> P2 --> P3
end
subgraph ops[File/Directory Operations]
O1[exists, is_regular_file]
O2[create_directories, remove]
O3[copy, rename]
O1 --> O2 --> O3
end
subgraph iter[Iteration]
I1[directory_iterator]
I2[recursive_directory_iterator]
I1 --> I2
end
path --> ops
path --> iter
Diagram explanation: path represents and combines paths. exists, create_directories, copy manipulate files/directories. directory_iterator traverses one level, recursive_directory_iterator traverses subdirectories.
Compile Options
# Requires C++17
g++ -std=c++17 -o fs_demo fs_demo.cpp
# Windows (MSVC)
# /std:c++17
Header and Namespace
Include <filesystem> and conventionally abbreviate with namespace fs = std::filesystem;.
3. Path Operations
Path Creation and Combination
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
// 1) Create from string
fs::path p1("/home/user/config");
fs::path p2("settings.json");
// 2) Combine with operator/ (OS separator auto-applied)
fs::path full = p1 / p2;
std::cout << full << "\n"; // Linux: /home/user/config/settings.json
// Windows: /home/user/config\settings.json
// 3) Current directory
fs::path cwd = fs::current_path();
std::cout << "CWD: " << cwd << "\n";
// 4) Executable path (pre-C++23 needs platform-specific API)
// Linux: /proc/self/exe, Windows: GetModuleFileName
}
Code explanation: path creates like string, operator/ combines with OS-appropriate separator. current_path() returns process working directory.
Path Component Extraction
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path p = "/home/user/projects/app/src/main.cpp";
std::cout << "filename: " << p.filename() << "\n"; // main.cpp
std::cout << "stem: " << p.stem() << "\n"; // main
std::cout << "extension: " << p.extension() << "\n"; // .cpp
std::cout << "parent: " << p.parent_path() << "\n"; // .../src
std::cout << "root_name: " << p.root_name() << "\n"; // (Linux: "")
std::cout << "root_path: " << p.root_path() << "\n"; // /
// Convert to relative path
fs::path base = "/home/user/projects";
fs::path rel = fs::relative(p, base);
std::cout << "relative: " << rel << "\n"; // app/src/main.cpp
}
Code explanation: filename() is last component, stem() excludes extension, extension() is .cpp, parent_path() is parent directory. relative(p, base) creates relative path from base.
Path Normalization and Absolute Path
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path p = "config/../config/settings.json";
// Normalize: remove . and ..
fs::path canonical = fs::weakly_canonical(p);
std::cout << "weakly_canonical: " << canonical << "\n";
// Absolute path (path must exist)
if (fs::exists(p)) {
fs::path abs = fs::absolute(p);
std::cout << "absolute: " << abs << "\n";
fs::path can = fs::canonical(p); // Resolves symlinks, must exist
std::cout << "canonical: " << can << "\n";
}
// Path comparison
fs::path a = "/a/b/c";
fs::path b = "/a/b/c";
std::cout << "equal: " << (a == b) << "\n";
}
Code explanation: weakly_canonical normalizes ./.. and works even if path doesn’t exist. canonical requires path to exist and resolves symlinks. absolute creates absolute path from current directory.
Complete Path Manipulation Example
// Copy-paste and run: g++ -std=c++17 -o path_demo path_demo.cpp && ./path_demo
#include <filesystem>
#include <iostream>
#include <string>
namespace fs = std::filesystem;
int main() {
fs::path base = fs::current_path();
fs::path configDir = base / "config";
fs::path settingsPath = configDir / "app" / "settings.json";
std::cout << "Base: " << base << "\n";
std::cout << "Config dir: " << configDir << "\n";
std::cout << "Settings: " << settingsPath << "\n";
std::cout << "Filename: " << settingsPath.filename() << "\n";
std::cout << "Stem: " << settingsPath.stem() << "\n";
std::cout << "Ext: " << settingsPath.extension() << "\n";
// Convert to string
std::string strPath = settingsPath.string(); // OS native format
std::string u8Path = settingsPath.u8string(); // UTF-8 (C++20)
}
4. Directory Iteration
directory_iterator: One Level Only
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path dir = ".";
for (const auto& entry : fs::directory_iterator(dir)) {
std::cout << entry.path().filename() << " - ";
if (entry.is_regular_file()) {
std::cout << "file, " << fs::file_size(entry) << " bytes\n";
} else if (entry.is_directory()) {
std::cout << "dir\n";
} else if (entry.is_symlink()) {
std::cout << "symlink -> " << fs::read_symlink(entry) << "\n";
}
}
}
Code explanation: directory_iterator traverses only direct children of specified directory. entry.path() gets path, entry.is_regular_file() checks type.
recursive_directory_iterator: Including Subdirectories
#include <filesystem>
#include <iostream>
#include <string>
namespace fs = std::filesystem;
void listAll(const fs::path& dir) {
for (const auto& entry : fs::recursive_directory_iterator(dir,
fs::directory_options::skip_permission_denied)) {
// depth(): current depth (0 = top level)
std::string indent(entry.depth() * 2, ' ');
std::cout << indent << entry.path().filename() << "\n";
}
}
Code explanation: recursive_directory_iterator recursively traverses subdirectories. entry.depth() provides indentation depth, skip_permission_denied skips directories without permission.
Filter by Extension
#include <filesystem>
#include <string>
#include <vector>
namespace fs = std::filesystem;
std::vector<fs::path> findFilesByExtension(const fs::path& dir,
const std::string& ext) {
std::vector<fs::path> result;
for (const auto& entry : fs::recursive_directory_iterator(dir,
fs::directory_options::skip_permission_denied)) {
if (entry.is_regular_file() && entry.path().extension() == ext) {
result.push_back(entry.path());
}
}
return result;
}
int main() {
auto cppFiles = findFilesByExtension(".", ".cpp");
for (const auto& p : cppFiles) {
std::cout << p << "\n";
}
}
Directory Iteration Error Handling
#include <filesystem>
#include <iostream>
#include <system_error>
namespace fs = std::filesystem;
bool safeListDir(const fs::path& dir) {
std::error_code ec;
auto iter = fs::directory_iterator(dir, ec);
if (ec) {
std::cerr << "Cannot open " << dir << ": " << ec.message() << "\n";
return false;
}
for (const auto& entry : fs::directory_iterator(dir, ec)) {
if (ec) {
std::cerr << "Iterator error: " << ec.message() << "\n";
return false;
}
std::cout << entry.path().filename() << "\n";
}
return true;
}
Code explanation: Passing std::error_code to directory_iterator constructor and increment handles failures with error codes instead of exceptions.
5. File Operations
File Existence and Type Check
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void checkPath(const fs::path& p) {
if (!fs::exists(p)) {
std::cout << "Does not exist\n";
return;
}
if (fs::is_regular_file(p)) {
std::cout << "Regular file, size: " << fs::file_size(p) << "\n";
} else if (fs::is_directory(p)) {
std::cout << "Directory\n";
} else if (fs::is_symlink(p)) {
std::cout << "Symlink -> " << fs::read_symlink(p) << "\n";
} else if (fs::is_block_file(p)) {
std::cout << "Block device\n";
} else if (fs::is_character_file(p)) {
std::cout << "Character device\n";
} else if (fs::is_fifo(p)) {
std::cout << "FIFO\n";
} else if (fs::is_socket(p)) {
std::cout << "Socket\n";
}
}
Code explanation: exists() checks existence, is_regular_file() etc. distinguish types. file_size() only works for regular files.
Directory Creation
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path dir = "a/b/c/d";
// create_directories: creates all intermediate paths
bool created = fs::create_directories(dir);
std::cout << "Created: " << created << "\n";
// create_directory: one level only (parent must exist)
fs::create_directory("single_dir");
// Returns false if already exists, true if created
if (fs::create_directories("logs/2026/03")) {
std::cout << "Log dir created\n";
}
}
Code explanation: create_directories creates all intermediate paths like mkdir -p, create_directory creates one level only.
File Copy
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path src = "source.txt";
fs::path dst = "backup/copy.txt";
// Options: overwrite_existing, recursive (for directories), copy_symlinks
fs::copy_options opts = fs::copy_options::overwrite_existing;
fs::copy_file(src, dst, opts); // Copy file only
// Copy entire directory
fs::copy("src_dir", "dst_dir",
fs::copy_options::recursive | fs::copy_options::overwrite_existing);
}
Code explanation: copy_file copies single file, copy can copy directories with recursive. Without overwrite_existing, error occurs if destination exists.
File Move/Rename
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path oldName = "temp.txt";
fs::path newName = "final.txt";
fs::rename(oldName, newName); // Same filesystem: atomic move
// Different filesystem: copy then delete
fs::path otherFs = "/mnt/other/disk/file.txt";
fs::rename(oldName, otherFs); // May use copy+remove
}
Code explanation: rename is atomic within same filesystem. Moving across filesystems may use copy+delete.
File Deletion
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path file = "junk.txt";
fs::path dir = "empty_dir";
// Delete file
if (fs::remove(file)) {
std::cout << "Removed " << file << "\n";
}
// Delete empty directory
fs::remove(dir);
// Delete directory tree (including contents)
fs::path tree = "to_delete";
std::uintmax_t n = fs::remove_all(tree);
std::cout << "Removed " << n << " items\n";
}
Code explanation: remove deletes file or empty directory, remove_all deletes including subdirectories and returns count of deleted items.
Symbolic Links
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path target = "real_file.txt";
fs::path link = "symlink_to_file";
fs::create_symlink(target, link); // Relative path link
// fs::create_directory_symlink(target, "symlink_to_dir"); // For directories
if (fs::is_symlink(link)) {
fs::path resolved = fs::read_symlink(link);
std::cout << "Link points to: " << resolved << "\n";
// Actual file path (absolute)
fs::path canonical = fs::canonical(link);
std::cout << "Canonical: " << canonical << "\n";
}
}
Code explanation: create_symlink creates symbolic link, read_symlink reads target path. canonical follows links to final path.
Complete File Copy Utility
// Copy-paste and run: g++ -std=c++17 -o fs_copy fs_copy.cpp && ./fs_copy src dst
#include <filesystem>
#include <iostream>
#include <system_error>
namespace fs = std::filesystem;
bool copyRecursive(const fs::path& src, const fs::path& dst) {
std::error_code ec;
if (!fs::exists(src, ec)) {
std::cerr << "Source does not exist: " << src << "\n";
return false;
}
if (fs::is_regular_file(src)) {
fs::create_directories(dst.parent_path(), ec);
if (ec) {
std::cerr << "Cannot create parent: " << ec.message() << "\n";
return false;
}
fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec);
if (ec) {
std::cerr << "Copy failed: " << ec.message() << "\n";
return false;
}
return true;
}
if (fs::is_directory(src)) {
fs::create_directories(dst, ec);
for (const auto& entry : fs::directory_iterator(src)) {
fs::path newDst = dst / entry.path().filename();
if (!copyRecursive(entry.path(), newDst)) {
return false;
}
}
return true;
}
std::cerr << "Unsupported type: " << src << "\n";
return false;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <source> <destination>\n";
return 1;
}
return copyRecursive(argv[1], argv[2]) ? 0 : 1;
}
6. Permissions
Check Permissions
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void showPermissions(const fs::path& p) {
if (!fs::exists(p)) return;
auto perms = fs::status(p).permissions();
auto has = [perms](fs::perms p) { return (perms & p) != fs::perms::none; };
std::cout << "Owner read: " << has(fs::perms::owner_read) << "\n";
std::cout << "Owner write: " << has(fs::perms::owner_write) << "\n";
std::cout << "Owner exec: " << has(fs::perms::owner_exec) << "\n";
std::cout << "Group read: " << has(fs::perms::group_read) << "\n";
std::cout << "Others read: " << has(fs::perms::others_read) << "\n";
}
Code explanation: fs::status(p).permissions() gets permission bits, AND with owner_read etc. to check.
Set Permissions
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
int main() {
fs::path file = "secret.txt";
// Owner read/write only (600)
fs::permissions(file,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace);
// Add execute permission (keep existing)
fs::permissions(file,
fs::perms::owner_exec,
fs::perm_options::add);
// Remove permissions
fs::permissions(file,
fs::perms::others_read | fs::perms::others_write,
fs::perm_options::remove);
}
Code explanation: replace replaces with specified permissions, add adds, remove removes. owner_read | owner_write corresponds to chmod 600.
Create File with Permissions
#include <filesystem>
#include <fstream>
namespace fs = std::filesystem;
void createRestrictedFile(const fs::path& path, const std::string& content) {
std::ofstream out(path);
if (!out) return;
out << content;
out.close();
// Restrict permissions immediately after creation (owner read/write only)
fs::permissions(path,
fs::perms::owner_read | fs::perms::owner_write,
fs::perm_options::replace);
}
Code explanation: After writing file, restrict permissions with permissions—applicable for uploads, config files, etc.
7. Common Errors and Solutions
Error 1: filesystem_error Exception Terminates Program
Symptom: fs::copy_file or fs::remove throws filesystem_error.
Cause: By default, std::filesystem functions throw exceptions on failure. Missing files or insufficient permissions cause exceptions.
Solution:
// ❌ Throws exception
fs::remove("nonexistent.txt"); // throws filesystem_error
// ✅ Use error_code
std::error_code ec;
fs::remove("nonexistent.txt", ec);
if (ec) {
std::cerr << "Remove failed: " << ec.message() << "\n";
}
Error 2: file_size Called on Directory
Symptom: fs::file_size(dir) throws exception or returns wrong result.
Cause: file_size only works for regular files.
Solution:
// ❌ Wrong usage
auto size = fs::file_size(somePath); // Error if directory
// ✅ Check type first
if (fs::is_regular_file(path)) {
auto size = fs::file_size(path);
}
Error 3: recursive_directory_iterator Stops on Permission Denied
Symptom: Traversing / or system directories throws exception.
Cause: Accessing directories without permission throws by default.
Solution:
// ✅ skip_permission_denied option
for (const auto& entry : fs::recursive_directory_iterator("/",
fs::directory_options::skip_permission_denied)) {
// Skips directories without permission
}
Error 4: Garbled Paths on Windows
Symptom: path << u8"한글" output is garbled.
Cause: Windows console default encoding may not be UTF-8.
Solution:
// Windows: set console to UTF-8
#ifdef _WIN32
#include <windows.h>
SetConsoleOutputCP(65001);
#endif
// Or use u8string() for UTF-8 string
std::string u8 = path.u8string();
Error 5: copy_file Doesn’t Overwrite Existing File
Symptom: Copy fails when destination file exists. Cause: Default behavior doesn’t overwrite existing files. Solution:
// ✅ overwrite_existing option
fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
Error 6: create_directories Fails
Symptom: create_directories("a/b/c") returns false.
Cause: If a already exists as regular file, can’t create directory under it. Or insufficient permissions.
Solution:
std::error_code ec;
bool ok = fs::create_directories(path, ec);
if (!ok && ec) {
std::cerr << "Create failed: " << ec.message()
<< " (path: " << path << ")\n";
}
// Check if a is a file
if (fs::exists("a") && fs::is_regular_file("a")) {
std::cerr << "Cannot create: 'a' is a file\n";
}
8. Best Practices
1. Use error_code to Disable Exceptions
In production, handling file system errors with std::error_code instead of exceptions is more stable.
std::error_code ec;
if (!fs::exists(path, ec) || ec) {
logError("Path check failed", ec);
return false;
}
2. Always Combine Paths with path
Use path and operator/ instead of string concatenation.
// ❌
std::string p = base + "/" + sub + "/" + file;
// ✅
fs::path p = fs::path(base) / sub / file;
3. Double-Check with exists + is_regular_file
Verify type before file operations.
if (fs::exists(p) && fs::is_regular_file(p)) {
auto size = fs::file_size(p);
}
4. Beware of TOCTOU
Between exists check and actual use, file may be deleted or changed. Ultimately verify actual operation result like ifstream::is_open().
if (fs::exists(p)) {
std::ifstream f(p);
if (!f) { /* Open failed - may have been deleted after exists */ }
}
5. Use skip_permission_denied for Recursive Traversal
When scanning entire system, use skip_permission_denied.
9. Production Patterns
Pattern 1: Safe Directory Creation
#include <filesystem>
#include <system_error>
namespace fs = std::filesystem;
bool ensureDirectory(const fs::path& dir) {
std::error_code ec;
if (fs::exists(dir, ec)) {
if (ec) return false;
return fs::is_directory(dir, ec);
}
return fs::create_directories(dir, ec) && !ec;
}
Pattern 2: Atomic File Write (temp + rename)
#include <filesystem>
#include <fstream>
#include <system_error>
namespace fs = std::filesystem;
bool atomicWrite(const fs::path& target, const std::string& content) {
std::error_code ec;
fs::path tmp = target.parent_path() / (target.filename().string() + ".tmp");
std::ofstream out(tmp, std::ios::binary);
if (!out) return false;
out << content;
out.close();
if (!out) {
fs::remove(tmp, ec);
return false;
}
fs::rename(tmp, target, ec);
if (ec) {
fs::remove(tmp, ec);
return false;
}
return true;
}
Explanation: Write to temp file first, then atomically rename to target. If write fails, target remains unchanged.
Pattern 3: Calculate Directory Size
#include <filesystem>
#include <cstdint>
namespace fs = std::filesystem;
std::uintmax_t directorySize(const fs::path& dir) {
std::uintmax_t total = 0;
std::error_code ec;
for (const auto& entry : fs::recursive_directory_iterator(dir,
fs::directory_options::skip_permission_denied, ec)) {
if (ec) return 0;
if (entry.is_regular_file(ec) && !ec) {
total += fs::file_size(entry, ec);
}
}
return total;
}
Pattern 4: Temp Directory RAII
#include <filesystem>
#include <random>
#include <sstream>
namespace fs = std::filesystem;
class TempDirectory {
fs::path path_;
public:
TempDirectory() {
std::ostringstream oss;
oss << fs::temp_directory_path() / "tmp_" << std::rand();
path_ = oss.str();
fs::create_directories(path_);
}
~TempDirectory() {
std::error_code ec;
fs::remove_all(path_, ec);
}
const fs::path& path() const { return path_; }
};
Pattern 5: Config File Path Resolution
#include <filesystem>
#include <string>
namespace fs = std::filesystem;
fs::path resolveConfigPath(const std::string& filename) {
// 1) Environment variable
const char* configHome = std::getenv("XDG_CONFIG_HOME");
if (configHome) {
fs::path p = fs::path(configHome) / "myapp" / filename;
if (fs::exists(p)) return fs::canonical(p);
}
// 2) Home directory
const char* home = std::getenv("HOME");
if (home) {
fs::path p = fs::path(home) / ".config" / "myapp" / filename;
if (fs::exists(p)) return fs::canonical(p);
}
// 3) Current directory
if (fs::exists(filename)) return fs::canonical(filename);
return {};
}
10. Checklist
Items to verify during implementation:
- Compile with
-std=c++17or higher - Use
pathandoperator/for path combination - Use
std::error_codeoverloads in production - Check
is_regular_filebeforefile_size - Apply
skip_permission_deniedtorecursive_directory_iterator - Check if
overwrite_existingneeded forcopy_file - Consider atomic write (temp + rename) for critical data
- Clean up temp files/directories with RAII
11. Summary
| Item | Content |
|---|---|
| path | Path representation, combine with operator/ |
| exists | Check existence |
| is_regular_file | Check if regular file |
| create_directories | Create including intermediate paths |
| directory_iterator | Traverse one level |
| recursive_directory_iterator | Traverse including subdirectories |
| copy_file / copy | Copy file/directory |
| remove / remove_all | Delete |
| permissions | Check/set permissions |
| Core Principles: |
- Handle cross-platform paths with
path - Handle errors without exceptions using
error_code - Verify type before file operations
- Maintain data integrity with atomic writes
Related articles (internal links)
These posts connect naturally with this topic.
- C++ File I/O | ifstream, ofstream, and Handling “File Open Failed” Errors
- C++ Early Look at C++23 Core Features [#37-1]
- C++ Exception Handling | try-catch-throw & Exceptions vs Error Codes
Practical checklist
Use this when applying these ideas in real projects.
Before you write code
- Is this technique the best fit for the problem at hand?
- Can teammates understand and maintain this code?
- Does it meet performance requirements?
While writing code
- Have you cleared all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
During code review
- Is the intent clear?
- Are tests sufficient?
- Is the behavior documented where it matters?
Keywords
C++ filesystem, std::filesystem, path operations, directory traversal, file copy, file permissions, cross-platform, C++17
Frequently asked questions (FAQ)
Q. When do I use this in production?
A. Config file paths, creating log directories, backup/restore, plugin scanning, cleaning up temp files—any C++ project that touches the file system. std::filesystem is the standard approach.
Q. What should I read first?
A. Start with File I/O basics (#11-1) and Exception handling (#8-1).
Q. How do I go deeper?
A. Read cppreference std::filesystem and your platform’s stat / chmod APIs.
One-line summary: Use std::filesystem::path to combine paths and handle the file system safely with create_directories, copy_file, and directory_iterator. Next, read File I/O basics (#11-1).
Previous: C++23 Core Features (#37-1)
Next: C++ Practical Guide #38-1: Clean Code Basics
More related posts
- Browse the full C++ series
- C++ Adapter Pattern | Interface adaptation and compatibility
- C++ ADL (Argument-Dependent Lookup)
- C++ Aggregate Initialization