C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
121: Working with the C++ Filesystem Library
I've seen a lot of developers approach file management in C++ as if they were still writing C in 1998. They treat paths as strings, use fopen or dirent.h, and spend half their afternoon debugging why their code works on Linux but crashes on Windows because of a misplaced backslash. If you've ever found yourself manually appending "/" or "\\" to a string to build a file path, you've been in the trenches. It's tedious, it's error-prone, and frankly, we have a much better tool now.
The fragility of string-based pathing
Let's look at a common task: scanning a directory for .log files and moving them to an archive folder. The naive way to do this is to treat everything as a string. You might use a platform-specific API to get a list of files and then use string concatenation to build the destination path.
// The "Don't do this" way
std::string sourceDir = "logs/";
std::string archiveDir = "archive/";
// ... imagine some platform-specific code here to get 'filename' ...
std::string oldPath = sourceDir + filename;
std::string newPath = archiveDir + filename;
std::rename(oldPath.c_str(), newPath.c_str());
On the surface, this looks fine. But it's a house of cards. What happens if sourceDir is passed as "logs" without the trailing slash? Your path becomes "logsfilename.log", and the program fails. What happens when you move this code to Windows, where the system expects backslashes? You end up writing a bunch of #ifdef _WIN32 macros just to handle a folder separator. I've spent far too many hours of my life chasing bugs that were literally just a missing slash in a string.
Letting the library handle the OS quirks
This is why the <filesystem> library (introduced in C++17) is a game-changer. It stops treating paths as mere sequences of characters and starts treating them as first-class objects. When you use std::filesystem::path, the library understands the semantics of the operating system you're compiling for. You don't need to worry about slashes because the / operator is overloaded to handle path joining intelligently.
#include <filesystem>
#include <iostream>
namespace fs = std::filesystem;
void archiveLogs(fs::path sourceDir, fs::path archiveDir) {
// Create the archive directory if it doesn't exist
if (!fs::exists(archiveDir)) {
fs::create_directory(archiveDir);
}
// directory_iterator handles the heavy lifting of OS-specific folder scanning
for (const auto& entry : fs::directory_iterator(sourceDir)) {
if (entry.is_regular_file() && entry.path().extension() == ".log") {
fs::path destination = archiveDir / entry.path().filename();
fs::rename(entry.path(), destination);
std::cout << "Archived: " << entry.path().filename() << "\n";
}
}
}
Notice the archiveDir / entry.path().filename() line. That / isn't division; it's a path append operator. It knows exactly which separator to use for your current OS and ensures there's exactly one separator between the parts. No more manual string checking.
The cost of convenience and safety
Now, there is a trade-off. std::filesystem operations can throw exceptions—like fs::filesystem_error—if a disk is read-only or a permission is denied. The C-style std::rename just returns a non-zero integer and leaves you to check errno. While exceptions might feel heavier, they are actually more honest about what's happening. In a real-world app, you don't want to silently fail to move a critical log file; you want to know exactly why it failed.
Additionally, directory_iterator is an abstraction. It's slightly slower than calling the raw Linux readdir system call because it's doing more work under the hood to provide a consistent interface. But unless you are writing a high-performance file indexer for a search engine, that overhead is negligible compared to the developer time you save by not writing platform-specific glue code.
📋 Practical Task
Build a Recursive Project Cleanup Tool
Your task is to create a utility that cleans up a project directory by finding and deleting all .tmp files, regardless of how deep they are in the folder hierarchy.
Requirements:
- Use
std::filesystem::recursive_directory_iteratorto ensure you find files in subfolders. - Check if the entry is a regular file and has the
.tmpextension. - Use
std::filesystem::remove()to delete the file. - Wrap the iteration in a
try-catchblock to handlestd::filesystem::filesystem_error, printing a helpful message if a directory is inaccessible. - Print the full path of every file that was successfully deleted.
There are no comments for now.