C++ std::filesystem::path | Cross-platform paths in C++17

C++ std::filesystem::path | Cross-platform paths in C++17

이 글의 핵심

Practical guide to std::filesystem::path: decomposition, appending with /, absolute vs canonical paths, extension changes, and batch rename examples.

Introduction

std::filesystem::path is the C++17 class for portable path handling.


1. What is path?

Basic usage

#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    fs::path p = "/home/user/file.txt";
    fs::path p2 = "C:\\Users\\user\\file.txt";  // Windows
    
    std::cout << p << std::endl;
    std::cout << p2 << std::endl;
    
    return 0;
}

Features:

  • Platform-independent
  • Automatic separator handling (/, \\)
  • Rich API for decomposition and manipulation

2. Path manipulation

Decomposition

#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

int main() {
    fs::path p = "/home/user/documents/report.pdf";
    
    std::cout << "Full: " << p << std::endl;
    std::cout << "Filename: " << p.filename() << std::endl;      // "report.pdf"
    std::cout << "Extension: " << p.extension() << std::endl;     // ".pdf"
    std::cout << "Stem: " << p.stem() << std::endl;             // "report"
    std::cout << "Parent: " << p.parent_path() << std::endl;    // "/home/user/documents"
    std::cout << "Root: " << p.root_path() << std::endl;          // "/"
    
    return 0;
}

Joining paths

fs::path dir = "/home/user";
fs::path file = "file.txt";

// / operator
fs::path full = dir / file;  // "/home/user/file.txt"

// /= operator
fs::path p = "/home/user";
p /= "documents";
p /= "file.txt";
std::cout << p << std::endl;  // "/home/user/documents/file.txt"

// append
fs::path p2 = "/home";
p2.append("user").append("file.txt");

3. Path normalization

Absolute paths

fs::path p = "file.txt";

fs::path abs = fs::absolute(p);
std::cout << abs << std::endl;  // e.g. "/current/directory/file.txt"

Canonical paths

fs::path p = "/home/user/../user/./file.txt";

// Canonical (target must exist)
fs::path canonical = fs::canonical(p);
std::cout << canonical << std::endl;  // "/home/user/file.txt"

// weakly_canonical (need not exist)
fs::path p2 = "/home/user/nonexistent.txt";
fs::path weak = fs::weakly_canonical(p2);
std::cout << weak << std::endl;

Relative paths

fs::path abs = "/home/user/documents/file.txt";
fs::path base = "/home/user";

fs::path rel = fs::relative(abs, base);
std::cout << rel << std::endl;  // "documents/file.txt"

4. Path properties

fs::path p = "/home/user/file.txt";

if (p.is_absolute()) {
    std::cout << "Absolute path" << std::endl;
}

if (p.is_relative()) {
    std::cout << "Relative path" << std::endl;
}

if (p.empty()) {
    std::cout << "Empty path" << std::endl;
}

if (fs::exists(p)) {
    std::cout << "Exists" << std::endl;
}

5. Extensions

Changing extensions

fs::path p = "document.txt";

p.replace_extension(".pdf");
std::cout << p << std::endl;  // "document.pdf"

p.replace_extension();
std::cout << p << std::endl;  // "document"

p.replace_extension(".docx");
std::cout << p << std::endl;  // "document.docx"

Checking extensions

#include <vector>
#include <algorithm>

fs::path p = "image.png";

if (p.extension() == ".png") {
    std::cout << "PNG file" << std::endl;
}

std::vector<std::string> image_exts = {".png", ".jpg", ".jpeg", ".gif"};
if (std::find(image_exts.begin(), image_exts.end(), p.extension()) != image_exts.end()) {
    std::cout << "Image file" << std::endl;
}

6. Comparing paths

fs::path p1 = "/home/user/file.txt";
fs::path p2 = "/home/user/file.txt";
fs::path p3 = "/home/user/other.txt";

if (p1 == p2) {
    std::cout << "Equal" << std::endl;
}

if (p1 != p3) {
    std::cout << "Different" << std::endl;
}

// Case sensitivity is platform-dependent
// Windows: often case-insensitive
// Linux: case-sensitive

7. Practical examples

Example 1: Batch extension rename

#include <filesystem>
#include <iostream>

namespace fs = std::filesystem;

void rename_extensions(const fs::path& dir, 
                       const std::string& old_ext, 
                       const std::string& new_ext) {
    for (const auto& entry : fs::directory_iterator(dir)) {
        if (entry.is_regular_file() && entry.path().extension() == old_ext) {
            fs::path new_path = entry.path();
            new_path.replace_extension(new_ext);
            fs::rename(entry.path(), new_path);
            std::cout << "Renamed: " << entry.path().filename() 
                      << " -> " << new_path.filename() << std::endl;
        }
    }
}

int main() {
    rename_extensions("./images", ".jpeg", ".jpg");
    return 0;
}

Example 2: Backup path

fs::path create_backup_path(const fs::path& original) {
    fs::path backup = original;
    backup.replace_extension();
    
    std::string backup_name = backup.filename().string() + "_backup";
    backup = backup.parent_path() / backup_name;
    backup.replace_extension(original.extension());
    
    return backup;
}

int main() {
    fs::path original = "/home/user/document.txt";
    fs::path backup = create_backup_path(original);
    
    std::cout << "Original: " << original << std::endl;
    std::cout << "Backup: " << backup << std::endl;
    
    return 0;
}

Summary

Key points

  1. path: portable path type in C++17
  2. / operator: join segments
  3. filename(), extension(), stem(): decomposition
  4. absolute(): resolve against current directory
  5. canonical(): full normalization (path must exist)

Common methods

MethodRole
filename()File name
extension()Extension including dot
stem()File name without extension
parent_path()Parent directory
root_path()Root component
replace_extension()Change extension

Next steps

  • C++ Filesystem
  • C++ Directory Iterator
  • C++ File Operations

  • C++ Directory Iterator |
  • C++ File Operations |
  • C++ File Status |
  • C++ Filesystem quick reference |
  • C++ Filesystem overview |