Concepts / Working with File Streams in C++

Working with File Streams in C++

Opening a file stream means creating a stream object and associating it with a file so the program can read or write through it.

  • Programming
Interactive lab

Try it: Reading and Writing Text Files

How open() returns a file object with a position pointer, how read(), read(n), readline(), readlines() and for line in fh advance it (and return '' at the end), how 'w' and 'a' modes change the file, and why a closed file (after close() or a with block) can no longer be read.

How it works

  1. open(name, 'r') starts the pointer at 0; 'w' empties the file first; 'a' starts at the end.
  2. read() returns everything from the pointer to the end; read(n) at most n characters; readline() up to and including the next \n; each moves the pointer past what it returned.
  3. At the end of the file every read returns '' (an empty string), which is how loops know to stop; for line in fh reads line by line the same way.
  4. write() text waits in a buffer and reaches the file when it is closed (or the program ends); reading a 'w'/'a' file or writing an 'r' file raises io.UnsupportedOperation.
  5. close(), or leaving a with block, closes the file; any further read raises ValueError: I/O operation on closed file.

Default run (10 steps): Ready: notes.txt holds 4 lines (51 characters). Nothing is open yet. … Finished. notes.txt now holds "From: stephen\nSubject: meeting\nFrom: louis\nThanks!\n".

Simplified: The lab's own small Python interpreter runs a fixed, hand-written script (you choose the variant and the values, never the code); nothing is executed as real Python. Every script was run in CPython 3.12 with the same inputs and the lab reproduces its output and error messages. One virtual text file with \n line endings (as on Linux/macOS), positions counted in characters (ASCII only), and a write buffer that is only flushed by close() or the end of the program.

Educational simulation

Loading the simulation…

Why a Program Needs a Stream

A program cannot read from or write to a file just by naming it. Before any file can be read or written, the program needs a stream connected to that file. A stream is the program's working channel to the file, and this article follows the lifecycle of that channel: creating it, connecting it to a file, checking whether the connection is usable, and then deciding what the program does next.

Opening a file stream is the step that makes this connection. The rest of the article focuses on one short check that sits right after that step, because that check is where a failed open first becomes visible to the program.

Creating a Stream and Associating It with a File

Definition

Opening a file stream means creating a stream object and associating it with a file so the program can read or write through it.

Opening a file stream has two parts. First, you create a stream object. Second, that object is associated with a particular file. The stream object is the program's handle for the file, and the association is what lets reads and writes travel to the correct place. Each part matters: an object that exists but is not connected to a file does not give the program access to that file.

Program variableofsthe program's name for thestreamStream objectthe program's handle forthe fileNamed file on diskfor example target.txtrefers toassociated with
The program works through a stream object, and the association is what connects that object to the named file on disk.

Reads and writes go through the stream, not directly to the name you typed. That is why the association step matters so much: if the stream never becomes connected to a usable file, every later read or write is working with a channel that does not lead anywhere useful.

Tracing the Check Right After Opening

Opening a stream does not guarantee that the file is ready to use. The check is not buried somewhere later in the program. It sits on the very next line after the stream is created, and that line decides what the program does next. Follow the path below and notice which line makes the decision.

Declare ofsstream object createdOpen target.txtstream associated with fileif (!ofs)is the stream not usable?Report errorbody of the if runsWrite to filenormal path continuesnext lineimmediately afternot usableusable
The program declares the stream, opens the named file, then checks the stream. The check decides whether the program reports an error or goes on to write.

What do you think happens?

Suppose the file named target cannot be opened. The stream object still exists. What does the conditional test see, and which branch does the program take?

Reveal answer
Answer:

The test sees a stream that is not in a usable state, so the body of the if runs and the program reports the error instead of writing.

The stream object exists, but it did not connect to a usable file. The exclamation mark negates the stream in the condition, so the body runs when the stream is not usable.

Reading if (!ofs) and if (!ifs)

Checking a stream's state uses a conditional such as if (!ofs) for an output stream or if (!ifs) for an input stream. The exclamation mark negates the stream in the condition, so the body of the if runs when the stream is not in a usable state. The condition and the response have the same shape for both directions. Only the stream and the message change.

Code walkthrough
#include <fstream>
#include <iostream>

int main() {
    std::fstream ifs("source.txt");
    if (!ifs) {
        std::cerr << "Could not open source.txt for reading" << std::endl;
        return 1;
    }
    std::cout << "source.txt is ready for reading" << std::endl;
    return 0;
}
  1. 1
    The program creates the stream ifs and associates it with source.txt in one step.
  2. 2
    The very next line checks ifs with if (!ifs), before any reading happens.
  3. 3
    If the stream is not usable, the body runs: the error message is printed and the program returns.
  4. 4
    If the stream is usable, the body is skipped and execution moves to the success message.
  • ifs is the stream object, and its name signals that it reads
  • The file name is given when the stream is declared
  • if (!ifs) tests whether the stream is not usable
  • The body reports the error and stops the program
Output
If source.txt cannot be opened:
Could not open source.txt for reading

If it opens:
source.txt is ready for reading

Stream State Before and After Open

The same conditional test gives different answers depending on the result of the open call. Think of the stream as moving from an attempted open into one of two states: usable, or not usable. The test is how the program reads that state.

Stream createdobject existsOpen attemptopen call on targetUsable streamif (!ofs) is falseNot usable streamif (!ofs) is trueopen calledopen succeedsopen fails
After the open call, the stream is either usable or not usable, and the if (!ofs) test sees whichever state results.

The object exists in both outcomes. What changes is whether it is connected to a usable file. This is why a stream object can be present in your code and still be useless. The check is the program's way of noticing the difference before it tries to write.

Mistakes With Failed Opens

  • Common mistake

    Assuming the stream is ready because the declaration ran

    What it looks like

    std::ofstream ofs("target.txt"); followed directly by writing to ofs with no check

    Why it fails

    Opening a file stream does not guarantee that the file is ready to use. Creating the object does not prove the association succeeded.

    Do this instead

    Place the if (!ofs) check on the very next line after the stream is created, before any read or write.

  • Common mistake

    Reading the test as true when the open succeeded

    What it looks like
    if (!ofs) { write the data here; }
    Why it fails

    The exclamation mark negates the stream, so the body runs when the stream is not usable, not when it is.

    Do this instead

    Put the error handling in the body of if (!ofs), and put the normal work after the check or in the else path.

  • Common mistake

    Looking for the failure deep inside the program

    What it looks like

    Searching the write logic for why nothing appears in the file

    Why it fails

    The failed open is first detected at the check right after the open, so the problem starts there even if it shows up later.

    Do this instead

    When a file operation misbehaves, look first at the check placed immediately after the open.

  • Common mistake

    Using the wrong direction name makes the code harder to read, such as naming an input stream ofs even though it reads from a file

Locating the First Failure in a Short Program

Work through the following short program in your head. The steps happen in this order: declare the stream, open the file, check the state, and only then write. The question is where a failed open first shows up as a problem.

cpp
EASY

If target.txt cannot be opened, on which line does the failed open first affect the program's path, and which line runs next?

Hints
  • Find the line that tests the stream immediately after the open call.
  • The body of if (!ofs) runs when the stream is not usable.
  • The write on the last line is never reached in that case.

Key Takeaways

  • Opening a file stream means creating a stream object and associating it with a file, so reads and writes can travel through that object.
  • Opening a stream does not guarantee the file is ready, so check the stream's state with if (!ofs) for output or if (!ifs) for input.
  • The exclamation mark negates the stream, so the body of the check runs when the stream is not in a usable state.
  • The check sits on the line right after the open, so that is where a failed open first shows up as a problem.

Related concepts

More concepts starting with W