Reading and writing text files in C++ is done with the stream classes [[std::ifstream]] and [[std::ofstream]], which behave exactly like [[std::cin]] and [[std::cout]] but read from and write to a file instead of the terminal. The important part is checking that the file opened: a stream that failed to open silently does nothing, and that is how a program ends up truncating a file it meant to append to.
Writing a file
An std::ofstream is opened with a filename, and anything written to it goes into that file.
#include
#include
#include
int main() {
std::ofstream file("members.txt");
if (!file) {
std::cerr << "could not open members.txt for writing\n";
return 1;
}
file << "m-001|Alice|2024\n";
file << "m-002|Bob|2025\n";
file << "m-003|Carol|2026\n";
file.close();
std::cout << "three records written\n";
return 0;
}The output confirms the write:
three records writtenNote the check on the stream immediately after opening. Opening a file for writing truncates it, so if the open fails partway through and you carry on writing, you can destroy the file you meant to keep.
Reading a file
An std::ifstream reads from a file. The most common pattern is to read a line at a time with std::getline until the stream runs out.
#include
#include
#include
int main() {
std::ifstream file("members.txt");
if (!file) {
std::cerr << "could not open members.txt for reading\n";
return 1;
}
std::string line;
int count = 0;
while (std::getline(file, line)) {
++count;
std::cout << "line " << count << ": " << line << '\n';
}
std::cout << "read " << count << " lines\n";
return 0;
}The output shows the three records read back:
line 1: m-001|Alice|2024
line 2: m-002|Bob|2025
line 3: m-003|Carol|2026
read 3 linesThe std::getline call returns the stream itself, which converts to false at the end of the file, so the loop condition is doing two jobs: reading a line and checking for the end.
The stream state
Every stream carries flags describing what has happened to it. The three you will use most are:
eof— the end of the input has been reached.fail— an operation failed, such as reading a number where there was text.bad— a serious error, such as a read error on the disk.
std::ifstream::good() is true when none of them is set, and it is the check to use after opening a file.
#include
#include
#include
int main() {
std::ifstream file("members.txt");
if (!file.good()) {
std::cerr << "the stream is not in a good state\n";
return 1;
}
std::string line;
std::getline(file, line);
std::cout << "first line: " << line << '\n';
std::cout << "good: " << file.good() << '\n';
std::cout << "eof: " << file.eof() << '\n';
std::cout << "fail: " << file.fail() << '\n';
std::cout << "bad: " << file.bad() << '\n';
return 0;
}The output shows the state of the stream after one line has been read:
first line: m-001|Alice|2024
good: 1
eof: 0
fail: 0
bad: 0Open modes
The second argument to the constructor or to .open() decides what happens to a file that already exists.
std::ios::out— write, truncating the file. This is the default forstd::ofstream.std::ios::app— write at the end, keeping the existing content.std::ios::in— read. This is the default forstd::ifstream.std::ios::binary— open in binary mode rather than text mode.
The app mode is what you want for a log file, and getting it wrong is how a log gets emptied on every run.
#include
#include
int main() {
{
std::ofstream file("members.txt", std::ios::app);
if (!file) {
std::cerr << "could not open for appending\n";
return 1;
}
file << "m-004|Dave|2026\n";
}
std::ifstream file("members.txt");
std::string line;
int count = 0;
while (std::getline(file, line)) {
++count;
}
std::cout << "records now: " << count << '\n';
return 0;
}The output shows the record being appended rather than replacing the file:
records now: 4Reading and writing in one go
A std::fstream can read and write, and the stream position is shared. The seekg and seekp functions move the position.
#include
#include
#include
int main() {
std::ofstream create("positions.txt");
create << "alpha\nbravo\ncharlie\n";
create.close();
std::fstream file("positions.txt", std::ios::in | std::ios::out);
if (!file) {
std::cerr << "could not open for reading and writing\n";
return 1;
}
file.seekg(0, std::ios::end);
std::cout << "size: " << file.tellg() << " bytes\n";
file.seekg(0, std::ios::beg);
std::string line;
std::getline(file, line);
std::cout << "first: " << line << '\n';
file.close();
return 0;
}The output shows the file size and the first line read back:
size: 20 bytes
first: alphaBinary mode and line endings
On Windows, a text-mode file translates \n into \r\n on write and back on read. On Linux it does not. If a file is written on one platform and read on another, the line endings may not match.
Opening with std::ios::binary turns the translation off, and it is the correct choice whenever the file format is defined in bytes rather than in lines.
#include
#include
int main() {
std::ofstream file("raw.bin", std::ios::binary);
if (!file) {
std::cerr << "could not open raw.bin\n";
return 1;
}
const char bytes[] = {'a', 'b', '\n', 'c'};
file.write(bytes, 4);
file.close();
std::ifstream check("raw.bin", std::ios::binary);
char buffer[4] = {0};
check.read(buffer, 4);
std::cout << "bytes read: " << check.gcount() << '\n';
std::cout << "third byte: " << static_cast(buffer[2]) << '\n';
return 0;
}The output shows all four bytes read back, with the newline preserved exactly as it was written:
bytes read: 4
third byte: 10Common file errors
- The file does not exist. An
std::ifstreamfails to open and the stream is notgood(). - The directory does not exist. Same symptom, and it is the usual cause of "it worked yesterday".
- Permission denied. The file exists but cannot be opened for writing.
- The file was truncated. You opened with
std::ofstreamand then checked the state too late.
Reading numbers from a file
Reading formatted values from a file uses the same extraction operators as reading from the keyboard, because an std::ifstream is an std::istream.
#include
#include
#include
int main() {
{
std::ofstream file("readings.txt");
file << "42 91 12\n7 5 3\n";
}
std::ifstream file("readings.txt");
if (!file) {
std::cerr << "could not open readings.txt\n";
return 1;
}
int first = 0;
int second = 0;
int third = 0;
int rows = 0;
while (file >> first >> second >> third) {
++rows;
std::cout << "row " << rows << ": " << first << ' ' << second << ' ' << third << '\n';
}
std::cout << "rows read: " << rows << '\n';
return 0;
}The output shows the two rows of three values each:
row 1: 42 91 12
row 2: 7 5 3
rows read: 2The extraction skips whitespace, so the exact spacing in the file does not matter. When the file runs out, the extraction fails and the loop ends — which is exactly the behaviour you want, and it also means a malformed line ends the loop rather than being silently skipped.
Quick reference
std::ofstreamwrites and truncates by default;std::ifstreamreads.- Always check the stream right after opening, before writing anything.
std::getlinereads a line and returns the stream, so it works as a loop condition.std::ios::appappends instead of truncating.std::ios::binarydisables newline translation.std::fstreamplusseekg,seekpandtellghandles random access.
Try it yourself: write three records to a file, then read them back and print them. Next, reopen the file in append mode and add a fourth record, and confirm the first three are still there. Finally, try to read a file that does not exist and print the stream state flags to see which one is set.