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
- path: portable path type in C++17
/operator: join segments- filename(), extension(), stem(): decomposition
- absolute(): resolve against current directory
- canonical(): full normalization (path must exist)
Common methods
| Method | Role |
|---|---|
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
Related posts
- C++ Directory Iterator |
- C++ File Operations |
- C++ File Status |
- C++ Filesystem quick reference |
- C++ Filesystem overview |