C++ Module 14 – Input/output, files, formatting and time
File handling in C++: ifstream and ofstream
Read and write text and binary files with std::ifstream and std::ofstream, choose the right open mode, and catch every error from open to close.
What you will learn
- Read and write text and binary files with std::ifstream, std::ofstream and std::fstream
- Choose an open mode such as app, trunc, ate, binary or noreplace for a task
- Detect and report file errors at open, write and close time
- Read a whole file into memory and copy binary files byte for byte
Before you start
What you’ll learn · Before you start
What you will learn
- Read and write text and binary files with std::ifstream, std::ofstream and std::fstream
- Choose an open mode such as app, trunc, ate, binary or noreplace for a task
- Detect and report file errors at open, write and close time
- Read a whole file into memory and copy binary files byte for byte
Before you start
On this page
A plain std::ofstream opened on notes.txt threw away its 11 bytes, first line and a line break, before the
program had written anything. C++ reads and writes files through streams from <fstream>: std::ifstream reads,
std::ofstream writes and std::fstream does both. Creating one opens the file, and its
destructor: The member function written ~T(), with no parameters and no return type, that runs when an object's lifetime ends: at the closing brace of its block, on a return, during stack unwinding, or after main for a global. More in “Destructors and RAII in C++” closes it, so a stream declared inside a block cleans up after itself even
when an exception leaves the block. Test the stream after opening, if (!in), because a missing file or a missing
permission makes the open fail without an exception.
The
The flags given when a file stream opens a file, such as std::ios::app or std::ios::binary. They decide whether an existing file is kept, emptied or refused, where writing starts, and whether line breaks are translated.
decides what happens to a file that exists: an std::ofstream opened without one empties the file at once,
std::ios::app appends to it, and C++23’s std::ios::noreplace refuses to touch it. Add std::ios::binary for data
that must stay byte for byte as it is. Text and numbers then go in and out with <<, >>, std::getline and
std::println, exactly as with the console.
The open mode decides what happens to a file that exists
| Mode | What it does to an existing file | Typical use |
|---|---|---|
out, or none for std::ofstream |
Empties it when it opens (the same as out with trunc) |
Writing a new report each time |
app |
Keeps it, and every write goes to the end | Logs and journals |
in with out |
Keeps it, and writes overwrite bytes from the start; fails if there is no file | Patching a fixed-size record |
ate |
Keeps the mode’s other rules, and starts at the end | Opening for reading or patching near the end |
noreplace (C++23) |
Fails if the file exists | Creating a file that must be new |
binary |
No translation of line breaks | Images, archives, any data that is not text |
This program puts first line into notes.txt, opens it with each mode, writes something and prints the file
afterwards, with its line breaks shown as \n:
// What each open mode does to a file that already holds "first line".
#include <filesystem>
#include <format>
#include <fstream>
#include <iterator>
#include <print>
#include <string>
// The whole file, with its line breaks shown as \n.
std::string contents(const char* path) {
std::ifstream in(path, std::ios::binary);
std::string text(std::istreambuf_iterator<char>(in), {});
return std::format("{:?}", text);
}
void reset() { std::ofstream("notes.txt") << "first line\n"; }
int main() {
reset();
{ std::ofstream out("notes.txt"); out << "new\n"; }
std::println("{:<34} {}", "ofstream, no mode (out | trunc)", contents("notes.txt"));
reset();
{ std::ofstream out("notes.txt", std::ios::app); out << "added\n"; }
std::println("{:<34} {}", "app", contents("notes.txt"));
reset();
{ std::ofstream out("notes.txt", std::ios::in | std::ios::out); out << "FIRST"; }
std::println("{:<34} {}", "in | out (no truncation)", contents("notes.txt"));
reset();
{ std::ofstream out("notes.txt", std::ios::in | std::ios::ate); out << "end\n"; }
std::println("{:<34} {}", "in | ate (starts at the end)", contents("notes.txt"));
reset();
{ std::ofstream out("notes.txt", std::ios::noreplace); std::println("{:<34} is_open() = {}", "noreplace (C++23), file exists", out.is_open()); }
std::filesystem::remove("missing.txt");
{ std::fstream f("missing.txt", std::ios::in | std::ios::out); std::println("{:<34} is_open() = {}", "fstream in | out, no such file", f.is_open()); }
} Output
ofstream, no mode (out | trunc) "new\n" app "first line\nadded\n" in | out (no truncation) "FIRST line\n" in | ate (starts at the end) "first line\nend\n" noreplace (C++23), file exists is_open() = false fstream in | out, no such file is_open() = false
Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined modes.cpp -o modes && ./modes
Both libraries printed the same lines. The modes behaved as the table says: the plain std::ofstream kept only
new, app kept the first line, in with out overwrote five bytes in place, and both noreplace on an existing
file and fstream on a missing one failed to open. Step through the six opens, one mode at a time:
The drawing shows the whole scene; its 7 steps are listed below.
Every step as text
- The starting file. Before every open,
reset()writesfirst lineand a line break into notes.txt: 11 bytes that each mode either keeps or throws away. - No mode. A plain
std::ofstreamopens without, which meansout | trunc: the file is emptied the moment it opens, so after writingnewit holds only that line. - app.
appkeeps the file and moves to its end before every write, soaddedwent after the first line. - in | out.
inwithoutkeeps the file and starts at its first byte, soFIRSToverwrote five bytes in place and the rest of the line stayed. - in | ate. With
in, nothing is emptied, andatestarts at the end, soendand a line break went after the first line. - noreplace.
noreplacerefuses a file that already exists: the open failed,is_open()printed false, and notes.txt was not touched. - A missing file. An
std::fstreamopened within | outneeds the file to exist. On missing.txt the open failed as well, so the program must test every open.
Checkpoint
Check: What each open mode does to a file
A stream opens in its constructor and closes at the end of its block
The second program writes a small CSV report with std::println, closes it, and reads it back field by field with
std::getline:
// Write a small CSV report, then read it back and check every row.
#include <fstream>
#include <ostream> // std::println(out, ...) for streams is declared here
#include <print>
#include <sstream>
#include <string>
#include <vector>
struct Row {
std::string city;
int orders;
double revenue;
};
int main() {
std::vector<Row> rows{{"Pune", 412, 61850.5}, {"Kochi", 97, 12040.0}, {"Jaipur", 233, 30115.25}};
{
std::ofstream out("report.csv");
if (!out) {
std::println(stderr, "cannot create report.csv");
return 1;
}
std::println(out, "city,orders,revenue");
for (const Row& r : rows) std::println(out, "{},{},{:.2f}", r.city, r.orders, r.revenue);
} // out is closed here, which also writes whatever is still buffered
std::ifstream in("report.csv");
if (!in) {
std::println(stderr, "cannot open report.csv");
return 1;
}
std::string line;
std::getline(in, line); // the header
double total = 0;
int count = 0;
while (std::getline(in, line)) {
std::istringstream fields(line);
std::string city, orders, revenue;
std::getline(fields, city, ',');
std::getline(fields, orders, ',');
std::getline(fields, revenue);
total += std::stod(revenue);
++count;
std::println("{:<8} {:>4} orders {:>10}", city, orders, revenue);
}
std::println("{} rows read back, revenue {:.2f}", count, total);
} Output
Pune 412 orders 61850.50 Kochi 97 orders 12040.00 Jaipur 233 orders 30115.25 3 rows read back, revenue 104005.75
Recorded with Apple clang version 21.0.0 (clang-2100.3.34.2) on macOS 26 arm64. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined report.cpp -o report && ./report
In the report, the block around the std::ofstream matters: its closing brace destroys the stream, which writes out
the last buffered bytes and closes the file before the std::ifstream opens it. Step through the program and watch
when the four lines really reach the file, and when the reading starts:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Open for writing.
std::ofstream out("report.csv")creates the file, or empties it if it is there already, andif (!out)checks that the open worked before anything is written. - Lines into the buffer.
std::println(out, …)writes the header and the three rows. They go into the stream's buffer first, so the file itself may still be empty. - The closing brace. The block ends, so the destructor of
outruns: it writes the buffered lines out to report.csv and closes the file. - Open for reading. Only now does
std::ifstream in("report.csv")open the file, so it finds every line. Hadoutstill been open, the last lines could have been waiting in its buffer. - Header, then rows. One
std::getlineskips the header. Each further line is split at its commas bystd::getline(fields, …, ','), which stops at a comma and removes it. - The total.
std::stodturns each revenue into a double, and the program prints its three rows and3 rows read back, revenue 104005.75, the same with both compilers.
One line is easy to forget: std::println(out, …) for a stream is declared in <ostream>, so include it yourself.
GCC’s <fstream> happens to bring it along, but the libc++ of Apple clang 21 rejects the call, as this program
without <ostream> shows:
// std::println on an std::ofstream, with <fstream> and <print> included but <ostream> left out.
#include <fstream>
#include <print>
int main() {
std::ofstream out("total.txt");
std::println(out, "total {}", 42);
std::println("wrote total.txt");
} from session import run
# no_ostream.cpp, which leaves out <ostream>, built with Apple clang (libc++), then with GCC 16 (libstdc++).
# Only the first three lines of each command's output are shown: the error, its line and the mark under it.
run("""
clang++ -std=c++23 -Wall -Wextra no_ostream.cpp -o no_ostream && ./no_ostream
g++-16 -std=c++23 -Wall -Wextra no_ostream.cpp -o no_ostream-gcc && ./no_ostream-gcc
""", max_lines=3) Output
$ clang++ -std=c++23 -Wall -Wextra no_ostream.cpp -o no_ostream && ./no_ostream
no_ostream.cpp:7:5: error: no matching function for call to 'println'
7 | std::println(out, "total {}", 42);
| ^~~~~~~~~~~~
[13 more lines]
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra no_ostream.cpp -o no_ostream-gcc && ./no_ostream-gcc
wrote total.txt
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 headers_session.py
Checkpoint
Check: Writing a file, closing it, reading it back
Text mode changes line breaks on Windows, binary mode never does
On Linux and macOS, text and binary mode behave the same. On Windows, text mode translates: writing turns each \n
into the two bytes \r\n, reading turns \r\n back into \n, and a Ctrl+Z byte read in text mode ends the input.
Text written and read on the same system survives that, but bytes of an image or a compressed file do not, so open
every non-text file with std::ios::binary and read it with read() and gcount() rather than with >>.
| On Windows | Text mode | Binary mode |
|---|---|---|
Writing \n |
Two bytes, \r\n |
One byte, \n |
Reading \r\n |
One \n |
Two bytes, \r and \n |
| Reading a Ctrl+Z byte | The end of the input | An ordinary byte |
On Linux and macOS both columns behave like the binary one, which is why a missing std::ios::binary can stay hidden
until the program is first built for Windows.
Checkpoint
Check: Text mode and binary mode
Write errors usually show at flush or close, not at the write
A file error can appear at three moments, and only the first is easy to notice:
Where written bytes wait, and where each error appears
Text description of the diagram
The diagram follows the bytes of a write from the program to the disk, from top to bottom.
- Opening: std::ofstream out(path) asks the operating system for the file. If it cannot be opened, for example because a folder is missing or the file is read-only, is_open() is false.
- Writing: out << text usually only copies the bytes into the stream's buffer, a block of the program's own memory, so the write itself rarely reports a problem.
- The buffer is handed to the operating system when it fills up, and at flush() or close(). This is where write errors, such as a full disk, show up as failbit or badbit.
- The operating system keeps its own copy and writes it to the disk later, or at once when the program calls fsync(). Until then, a power cut can still lose data that the program has already written and closed.
- At open,
is_open()is false: the folder is missing, the name is wrong or the permission is missing. - At write, almost nothing is reported, because
<<usually only fills the stream’s buffer. - At flush or close, the buffer goes to the operating system, and a full disk or a quota sets failbit or badbit.
A destructor cannot report anything, so call close() yourself and test the stream afterwards wherever an
incomplete file would be a problem. Writing the new version to a temporary file and renaming it over the old one,
once it is complete, keeps a crash during a save from destroying the old file. With a stream, a whole file reads
fastest when you ask for its size and call read() once.
Key idea
Test a file stream after it opens and again after you close it yourself: most write errors show only when the buffer is handed over at a flush or at close, and a destructor reports nothing.
Checkpoint
Check: Where file errors show up
Practise: open modes, text mode and errors
Open modes, closing before reading, text and binary mode, and where errors surface: the questions below go over all four, starting from the one you know least, and ask any you miss once more at the end.
Practice that adapts to you
Practise this lesson
12 questions about 7 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Errors at open, write and close, reproduced on both libraries Pro: opens with a Pro, Premium or Ultimate pass
Each failure in this part is produced on purpose and run with both standard libraries, so you can see which flags and error numbers each one leaves and write checks that work with both.
In this part:
- What a failed open tells you, and what errno adds
- A full disk that only shows at close, reproduced with a file-size limit
- Saving without ever losing the old file, with a crash reproduced
1 diagram · 6 runnable examples (C header, C++, Python) · about 6 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Reading whole files and copying bytes, measured Pro: opens with a Pro, Premium or Ultimate pass
How a program reads a whole file can change its speed many times over without changing its result. This part times five common ways on a 20 MB file with both libraries and copies a binary file in a way that a checksum proves exact.
In this part:
- Five ways to read 20 MB, timed on both libraries
- When the size is not known in advance
- A byte-exact copy, proved with a checksum
3 runnable examples (C++, Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Exercise: a tiny line-oriented database Pro: opens with a Pro, Premium or Ultimate pass
This exercise puts the whole lesson together: appending with the right mode, reading records back line by line, and rewriting a file so that a crash can never leave half of it.
In this part:
- What the three functions must do
- The starter, the sample tests and a first run
- Rewriting the file safely when a record is removed
2 runnable examples (C header, C++, Python) · about 4 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Interview questions
-
Fresher · Warm-up
What is the difference between opening a file with std::ios::app and with std::ios::ate?
Both start at the end of an existing file, but only app keeps every write there. With app, each write first moves to the end, so a log opened in append mode can never overwrite what is already in it, even after a seek. ate only places the position at the end once, when the file opens; after that the stream behaves as its other mode bits say, so a seek can move it and later writes go wherever the position points. There is one more trap: ate does not stop truncation, so an std::ofstream opened with ate alone still empties the file, because its mode is out, which truncates. Combine ate with in, as in in | ate, to keep the contents.
-
Mid to senior
A desktop app saves settings with std::ofstream out(path); out << settings;. Some users report that after a crash or a power cut their settings file is empty or cut short. Explain how that happens and design a save that never loses the old settings.
-
Mid to senior
A service writes its audit log through an std::ofstream and never checks the stream. When the disk filled up, the log silently stopped and nobody noticed for days. Where do write errors show up with file streams, and how would you make the logger notice and report them?
-
Fresher to mid
A file-copy function written with std::ifstream and std::ofstream copies images correctly on Linux and macOS, but on Windows the copies are corrupted and slightly larger. Explain why, fix it, and say how you would prove that a copy is exact.
Key takeaways
std::ifstream,std::ofstreamandstd::fstreamopen in their constructor and close in their destructor; test the stream after opening.- An
std::ofstreamwithout a mode empties the file; useappto append,inwithoutto patch,noreplaceto create only. - Open every file that is not text with
std::ios::binary, because text mode on Windows translates line breaks. - Errors from writing usually appear at flush or close, so close the file yourself and check the stream afterwards.
References
- Working Draft, Programming Languages: C++ (N4950) (ISO/IEC JTC1/SC22/WG21)
- P2467R1: Support exclusive mode for fstreams (ISO/IEC JTC1/SC22/WG21)
- P1759R6: Native handles and file streams (ISO/IEC JTC1/SC22/WG21)
- fopen, _wfopen (Microsoft C runtime library reference) (Microsoft)
- rename (The Open Group Base Specifications, POSIX.1-2024) (The Open Group and IEEE)
- fsync (The Open Group Base Specifications, POSIX.1-2024) (The Open Group and IEEE)
- getrlimit, setrlimit (The Open Group Base Specifications, POSIX.1-2024) (The Open Group and IEEE)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress