C++ Files and Stream I/O
Read and write files with RAII stream objects, validate open and parse states, choose a data format deliberately, and avoid fragile machine-specific paths.
Before this lesson
Open read and write text files safely
Distinguish end-of-file from malformed input
Choose a simple format without unsafe delimiter assumptions
The short answer
Use ifstream for reading and ofstream for writing, check the stream before using data, and let the stream destructor close the file. Treat paths, formats, encoding, and partial writes as part of the program contract.
File streams are RAII owners
std::ifstream owns an input file handle and std::ofstream owns an output handle. Construction attempts to open the path; stream state reports whether it succeeded. When the stream leaves scope, its destructor closes the handle even after an early return.
Opening can fail because a path is missing, permissions deny access, or a device is unavailable. Report enough context for the user to act, but avoid exposing secrets or assuming every missing file is an error—a first-run data file may be optional.
#include <fstream>
#include <iostream>
#include <string>
int main()
{
std::ifstream input{"notes.txt"};
if (!input)
{
std::cerr << "notes.txt is unavailable\n";
return 1;
}
std::string line;
while (std::getline(input, line))
std::cout << line << '\n';
}Read in the shape of the format
Line-oriented formats should use getline; whitespace-delimited numeric data can use formatted extraction; structured formats should use a parser that implements their grammar. Drive the loop from the read result so processing happens only after successful input.
Distinguish normal end-of-file from a malformed record or I/O failure. If a malformed line can be skipped, report its line number. If partial data would be dangerous, reject the whole load and keep the previous valid state.
| Format | Read approach | Validation |
|---|---|---|
| One record per line | getline | Parse each complete line |
| Whitespace numbers | operator>> | Check extraction and range |
| CSV | CSV library | Quotes, delimiters, newlines |
| JSON | JSON library | Schema and field types |
| Binary | Defined binary protocol | Version, size, byte order |
Paths belong at the application boundary
Core calculations should not discover files from hidden working-directory assumptions. Pass a path or stream into the storage boundary. Tests can then use a temporary location or string stream without changing domain logic.
The filesystem library provides path operations and directory queries. Keep path values as paths rather than assembling separators by hand, and never trust an external path without considering traversal and overwrite rules.
Persistence needs a compatibility contract
A saved format becomes an interface with earlier versions of the program. Define field meaning, escaping, optional values, and version changes. Hand-written delimiter splitting fails when user text contains that delimiter.
For important data, write a complete replacement to a temporary file, check the result, then replace the target according to platform guarantees. Backups, locking, and concurrent writers are product decisions beyond the syntax of ofstream.
Quick knowledge check
Answer before you reveal.
01Why is while (!input.eof()) usually wrong?
EOF is set after a read attempts to pass the end; drive the loop from the read operation itself instead.
02Does a successful open guarantee every write succeeds?
No. Later writes or flushing can fail and should be checked when durability matters.
Exercise
Practice challenge
Save study sessions as tab-separated topic and minute lines, then reload them while rejecting malformed or non-positive records with line numbers.
Requirements
- Missing first-run input is handled deliberately
- Malformed records are not added to valid data
- The stream closes without an explicit close call
Optional extension: Write to a temporary file and rename it only after a successful complete write.
Open in C++ compilerLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Should I call close manually?
Usually scope-based destruction closes the file. An explicit close is useful only when the result must be checked or the file released early.
Is CSV just splitting on commas?
No. Quoting, embedded delimiters, newlines, and escaping require a real parser for general CSV.
Why avoid absolute paths in source code?
They are machine-specific. Accept a path, use configuration, or derive an application-appropriate location.