Chapter 17: C++ File Input/Output (I/O) and Stream Manipulation
By Ali Naqi • December 09, 2025
Chapter 17: File Input/Output (I/O) and Stream Manipulation
A C++ program's ability to run complex algorithms and manage sophisticated objects (like those you designed in the OOP chapters) is only useful if it can persist its data. Programs must be able to remember data between sessions, save user preferences, and generate reports. This is achieved through **File Input/Output (I/O)**.
C++ handles file operations through a consistent concept called **Streams**. You are already familiar with three standard streams:
std::cin(Console **In**put Stream)std::cout(Console **Out**put Stream)std::cerr(Console **Err**or Stream)
To work with files, we use similar stream objects. This chapter focuses on the stream class hierarchy and how to safely read and write data to disk.
Section 1: The Stream Classes
To perform file operations, you must include the <fstream> header. This header provides the three main classes used for file I/O, all of which are derived from the generic stream classes:
-
std::ofstream(Output File Stream): Used exclusively for **writing** data to a file. (Like a printer.) -
std::ifstream(Input File Stream): Used exclusively for **reading** data from a file. (Like a scanner.) -
std::fstream(File Stream): Can be used for **both reading and writing**.
Writing Data to a File (std::ofstream)
The process of writing data is very similar to using std::cout, utilizing the stream insertion operator (<<).
#include <fstream>
#include <iostream>
#include <string>
void write_to_file(const std::string& filename) {
// 1. Create an ofstream object
std::ofstream outputFile(filename);
// 2. Always check if the file was opened successfully
if (outputFile.is_open()) {
// 3. Write data using the stream insertion operator (<<)
outputFile << "Log Entry: Program started successfully." << std::endl;
outputFile << "User ID: 1002" << std::endl;
outputFile << "Result: Success\n";
// 4. Close the file stream (optional but good practice)
outputFile.close();
std::cout << "Data successfully written to " << filename << std::endl;
} else {
std::cerr << "Error: Unable to open file " << filename << " for writing." << std::endl;
}
// RAII: If outputFile.close() is omitted, the destructor ~ofstream() closes the file automatically
// when outputFile goes out of scope.
}
Reading Data from a File (std::ifstream)
Reading data is similar to using std::cin, utilizing the stream extraction operator (>>) or line-by-line reading with std::getline.
void read_from_file(const std::string& filename) {
// 1. Create an ifstream object
std::ifstream inputFile(filename);
// 2. Always check if the file was opened successfully
if (inputFile.is_open()) {
std::string line;
// 3. Read the file line by line until the end of the file is reached
while (std::getline(inputFile, line)) {
std::cout << "READ: " << line << std::endl;
}
// 4. Close the file stream
inputFile.close();
} else {
std::cerr << "Error: Unable to open file " << filename << " for reading." << std::endl;
}
}
Section 2: File Open Modes (std::ios_base::openmode)
When you open a file, you can specify an open mode flag to control how the file is accessed. These flags are combined using the bitwise OR operator (|).
Using Open Modes (Appending)
If you want to add new data to an existing file instead of wiping its contents, you use the append mode (`std::ios::app`).
void append_to_file(const std::string& filename) {
// Open the file for output, specifically using the append mode
std::ofstream logFile(filename, std::ios::out | std::ios::app);
if (logFile.is_open()) {
logFile << "Appended Log: Operation completed at " << __TIME__ << std::endl;
logFile.close();
std::cout << "New log entry appended." << std::endl;
} else {
std::cerr << "Error: Could not open log file for appending." << std::endl;
}
}
Section 3: Stream State and Error Handling
After a stream operation, it's vital to check the state of the stream to ensure the operation succeeded. If a stream fails (e.g., trying to read past the end of the file, or trying to write to a full disk), flags are set:
-
stream.fail(): Returns true if a read/write operation failed (e.g., invalid data format). -
stream.bad(): Returns true if a non-recoverable error occurred (e.g., hardware failure). -
stream.eof(): Returns true if the end-of-file has been reached. -
stream.good(): Returns true if all flags are clear (the stream is ready for I/O).
The common loop condition while (stream >> var) implicitly checks the state of the stream after the extraction operation.
Chapter 17 Conclusion and Next Steps
You now know how to use the fstream library to bring data into your program and save data out to the disk, bridging the gap between volatile memory and persistent storage. Mastering file I/O is essential for any real-world application.
However, stream operations aren't the only place where things can go wrong. Whenever a function encounters a situation it cannot handle (like a file not being found, or a network failure), the program needs a structured way to stop normal execution and jump to recovery code.
In **Chapter 18**, we will study **Error Handling with Exceptions** (`try`, `catch`), the modern C++ way to deal with unexpected failures cleanly and safely.