C++ File Handling: fstream, std::filesystem, Paths & Binary I/O Complete Masterclass
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.
| Class | Direction | Default Mode | Use For |
|---|---|---|---|
std::ifstream | Read only | ios::in | Reading files |
std::ofstream | Write only | ios::out | ios::trunc | Writing (overwrites) |
std::fstream | Read & Write | ios::in | ios::out | Read-modify-write |
| Mode Flag | Meaning | Combine With |
|---|---|---|
ios::in | Open for reading | ifstream, fstream |
ios::out | Open for writing | ofstream, fstream |
ios::app | Always write at end (append) | ofstream |
ios::ate | Seek to end after open | any |
ios::trunc | Truncate/erase existing content | ofstream |
ios::binary | Binary mode (no newline translation) | any |
#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;
}
#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;
}
#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;
}
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.