C++ File Handling: fstream, std::filesystem, Paths & Binary I/O Complete Masterclass

โšก Modern C++ (C++17 / C++20 / C++23) ๐ŸŸข Lesson 19 ๐Ÿ“‚ Phase 19: File Handling & Filesystem ๐Ÿ“… 2026 Master Edition
๐Ÿ“Œ Covered in this in-depth guide: File Stream Classes ยท Open Modes (in/out/app/binary) ยท getline & read ยท write & append ยท seekg/seekp & tellg ยท readAll via rdbuf ยท Binary I/O & struct serialization ยท std::filesystem paths ยท directory_iterator ยท recursive_directory_iterator ยท file copy/rename/remove

Welcome to Phase 19 (Chapter 19): C++ File Handling & Filesystem Masterclass! C++ provides std::ifstream, std::ofstream, and std::fstream for high-level text and binary I/O. C++17 adds the powerful std::filesystem library for cross-platform path manipulation, directory traversal, file metadata, copying, renaming, and deletion โ€” all without system-specific OS calls.

1File Stream Classes & Open Modes
ClassDirectionDefault ModeUse For
std::ifstreamRead onlyios::inReading files
std::ofstreamWrite onlyios::out | ios::truncWriting (overwrites)
std::fstreamRead & Writeios::in | ios::outRead-modify-write
Mode FlagMeaningCombine With
ios::inOpen for readingifstream, fstream
ios::outOpen for writingofstream, fstream
ios::appAlways write at end (append)ofstream
ios::ateSeek to end after openany
ios::truncTruncate/erase existing contentofstream
ios::binaryBinary mode (no newline translation)any
2Reading & Writing Text Files
C++ โ€” Complete text file read/writeโ–ถ Run in Compiler
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <stdexcept>

// Write CSV data to file
void writeCSV(const std::string& filename,
              const std::vector<std::vector<std::string>>& rows) {
    std::ofstream file(filename);
    if (!file.is_open())
        throw std::runtime_error("Cannot open for writing: " + filename);
    for (const auto& row : rows) {
        for (std::size_t i = 0; i < row.size(); ++i) {
            if (i) file << ',';
            file << row[i];
        }
        file << '
';
    }
    std::cout << "Written " << rows.size() << " rows to " << filename << "
";
}  // RAII: file automatically closed here

// Read all lines
std::vector<std::string> readLines(const std::string& filename) {
    std::ifstream file(filename);
    if (!file)
        throw std::runtime_error("Cannot open for reading: " + filename);
    std::vector<std::string> lines;
    std::string line;
    while (std::getline(file, line)) {
        lines.push_back(line);
    }
    return lines;
}

// Read entire file into string
std::string readAll(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) throw std::runtime_error("Cannot open: " + filename);
    std::ostringstream oss;
    oss << file.rdbuf();   // read entire buffer
    return oss.str();
}

// Read word by word
void readWords(const std::string& filename) {
    std::ifstream file(filename);
    std::string word;
    std::cout << "Words: ";
    while (file >> word) std::cout << "[" << word << "] ";
    std::cout << "
";
}

// Append to file
void appendLog(const std::string& logFile, const std::string& message) {
    std::ofstream file(logFile, std::ios::app);  // append mode
    if (!file) throw std::runtime_error("Cannot open log: " + logFile);
    file << "[LOG] " << message << '
';
}

// seekg/seekp โ€” random access
void seekDemo(const std::string& filename) {
    std::fstream file(filename, std::ios::in | std::ios::out);
    if (!file) return;

    file.seekg(0, std::ios::end);   // seek to end
    auto fileSize = file.tellg();   // get position = file size
    std::cout << "File size: " << fileSize << " bytes
";

    file.seekg(0, std::ios::beg);   // back to beginning
    std::string firstLine;
    std::getline(file, firstLine);
    std::cout << "First line: " << firstLine << "
";
}

int main() {
    const std::string csvFile = "students.csv";
    const std::string logFile = "app.log";

    writeCSV(csvFile, {
        {"Name", "Score", "Grade"},
        {"Alice", "95", "A"},
        {"Bob", "87", "B"},
        {"Charlie", "72", "C"},
        {"Diana", "98", "A+"}
    });

    auto lines = readLines(csvFile);
    std::cout << "Read " << lines.size() << " lines:
";
    for (const auto& l : lines) std::cout << "  " << l << "
";

    appendLog(logFile, "Application started");
    appendLog(logFile, "CSV read successfully");

    std::string content = readAll(logFile);
    std::cout << "Log contents:
" << content;

    seekDemo(csvFile);
    return 0;
}
3Binary File I/O
C++ โ€” Binary read/write, struct serializationโ–ถ Run in Compiler
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <cstring>

#pragma pack(push, 1)  // prevent padding in struct
struct StudentRecord {
    char name[32];
    int rollNo;
    float gpa;
    int year;
};
#pragma pack(pop)

void writeBinary(const std::string& filename, const std::vector<StudentRecord>& records) {
    std::ofstream file(filename, std::ios::binary);
    if (!file) throw std::runtime_error("Cannot write binary: " + filename);

    uint32_t count = (uint32_t)records.size();
    file.write(reinterpret_cast<const char*>(&count), sizeof(count));    // header: record count
    file.write(reinterpret_cast<const char*>(records.data()),             // all records at once
               (std::streamsize)(records.size() * sizeof(StudentRecord)));
    std::cout << "Written " << count << " binary records
";
}

std::vector<StudentRecord> readBinary(const std::string& filename) {
    std::ifstream file(filename, std::ios::binary);
    if (!file) throw std::runtime_error("Cannot read binary: " + filename);

    uint32_t count;
    file.read(reinterpret_cast<char*>(&count), sizeof(count));

    std::vector<StudentRecord> records(count);
    file.read(reinterpret_cast<char*>(records.data()),
              (std::streamsize)(count * sizeof(StudentRecord)));
    return records;
}

int main() {
    std::vector<StudentRecord> students;
    auto makeRecord = [](const char* name, int roll, float gpa, int year) {
        StudentRecord r{};
        std::strncpy(r.name, name, sizeof(r.name) - 1);
        r.rollNo = roll; r.gpa = gpa; r.year = year;
        return r;
    };
    students.push_back(makeRecord("Alice",   101, 9.2f, 2));
    students.push_back(makeRecord("Bob",     102, 8.7f, 3));
    students.push_back(makeRecord("Charlie", 103, 9.5f, 1));

    writeBinary("students.dat", students);
    auto loaded = readBinary("students.dat");

    std::cout << "Loaded " << loaded.size() << " records:
";
    for (const auto& s : loaded) {
        std::cout << "  Roll:" << s.rollNo << " Name:" << s.name
                  << " GPA:" << s.gpa << " Year:" << s.year << "
";
    }
    return 0;
}
4std::filesystem (C++17) โ€” Paths, Dirs & File Operations
C++ โ€” std::filesystem complete APIโ–ถ Run in Compiler
#include <filesystem>
#include <iostream>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

void demonstratePaths() {
    fs::path p1 = "data/logs/app.log";
    std::cout << "path:        " << p1 << "
";
    std::cout << "filename:    " << p1.filename() << "
";       // app.log
    std::cout << "stem:        " << p1.stem() << "
";           // app
    std::cout << "extension:   " << p1.extension() << "
";      // .log
    std::cout << "parent_path: " << p1.parent_path() << "
";    // data/logs

    fs::path p2 = "/home/user";
    fs::path p3 = p2 / "documents" / "report.pdf";  // path concatenation
    std::cout << "joined:      " << p3 << "
";

    // Replace extension
    fs::path p4 = "image.jpg";
    p4.replace_extension(".png");
    std::cout << "replaced ext: " << p4 << "
";
}

void demonstrateDirectories() {
    // Create directories (all parent dirs too)
    fs::create_directories("output/temp/data");
    std::cout << "Created directories
";

    // Write test files
    for (const char* name : {"a.txt", "b.txt", "c.cpp", "d.hpp"}) {
        std::ofstream(std::string("output/temp/") + name) << "test content
";
    }

    // Directory iteration
    std::cout << "Contents of output/temp:
";
    for (const auto& entry : fs::directory_iterator("output/temp")) {
        std::cout << "  "
                  << (fs::is_directory(entry) ? "[DIR] " : "[FILE] ")
                  << entry.path().filename()
                  << " (" << (fs::is_regular_file(entry) ? std::to_string(fs::file_size(entry)) + "B" : "-")
                  << ")
";
    }

    // Recursive iteration
    std::cout << "
Recursive contents:
";
    for (const auto& entry : fs::recursive_directory_iterator("output")) {
        std::cout << "  " << std::string(entry.depth() * 2, ' ')
                  << entry.path().filename() << "
";
    }
}

void demonstrateFileOps() {
    // Copy
    fs::copy("output/temp/a.txt", "output/temp/a_backup.txt",
             fs::copy_options::overwrite_existing);
    std::cout << "Copied a.txt to a_backup.txt
";

    // Rename/move
    fs::rename("output/temp/b.txt", "output/temp/b_renamed.txt");
    std::cout << "Renamed b.txt
";

    // Remove single file
    fs::remove("output/temp/c.cpp");
    std::cout << "Removed c.cpp
";

    // File metadata
    auto p = fs::path("output/temp/a.txt");
    std::cout << "a.txt exists: " << std::boolalpha << fs::exists(p) << "
";
    std::cout << "a.txt size:   " << fs::file_size(p) << " bytes
";
    std::cout << "is_regular:   " << fs::is_regular_file(p) << "
";

    // Space info
    auto space = fs::space(".");
    std::cout << "Disk free: " << space.free / 1024 / 1024 << " MB
";

    // Cleanup
    fs::remove_all("output");  // delete entire tree
    std::cout << "Cleaned up output directory
";
}

int main() {
    std::cout << "=== Path Operations ===
";
    demonstratePaths();
    std::cout << "
=== Directory Operations ===
";
    demonstrateDirectories();
    std::cout << "
=== File Operations ===
";
    demonstrateFileOps();
    return 0;
}
5Technical FAQs

Q1: Do I need to explicitly close() a file stream?

No โ€” RAII handles it. The stream destructor calls close() automatically when it goes out of scope. Explicit close() is only needed when you want to flush and reopen within the same scope, or check the close return status.

Q2: How to read entire file into std::string efficiently?

Use std::ostringstream oss; oss << file.rdbuf(); for simple cases. For large files, file.seekg(0, ios::end); auto size = file.tellg(); file.seekg(0, ios::beg); string s(size, ''); file.read(s.data(), size); avoids a copy.

Q3: What is the difference between ios::app and ios::ate?

ios::app forces every write to the end of file (even after seeking). ios::ate seeks to the end on open but allows writing anywhere after that. Use ios::app for true append-only logs.

Q4: How to traverse directories recursively?

Use std::filesystem::recursive_directory_iterator: for (const auto& entry : fs::recursive_directory_iterator("root")) { ... }. Use entry.depth() to get the nesting level.

Q5: What is std::filesystem::path advantage over string?

fs::path is cross-platform (handles / vs separators), provides decomposition methods (filename(), extension(), parent_path()), and supports natural path concatenation with / operator.