Concepts / File Stream Best Practices

File Stream Best Practices

Because the stream classes are left alone, your new operator sits alongside the existing ones and extends what the stream can handle.

  • Programming

Why a Custom Value Cannot Print Itself

Suppose you have written a type that represents a point on a plane, a calendar date, or a bank account. You create an object of that type and try to send it to the output stream the same way you would send a number or a word. Built-in values already know how to appear on a screen or in a file. Your own type does not. The stream has no idea how your type should look as text, so you have to tell it.

The source material states the goal directly: you can define << and >> operators for user-defined types whose values have a meaningful textual representation. The phrase meaningful textual representation is the key condition. If a value can be sensibly written as text, and read back from text, it is a good candidate for input and output operators. This article focuses on the output side with << and then shows how the same approach extends to input with >>.

Built-in Values Versus Your Own Types

Think of the stream as a general-purpose channel. It carries text to a destination, such as a screen or a file. When you write an integer or a floating-point number into it, the stream already has the knowledge needed to turn that value into characters. A custom type is different. Its fields might be numbers, but the meaningful picture of the whole object, such as the two coordinates of a point written in parentheses, is something only your type can define.

Writing a Free Output Operator

You can define the << operator for your own type. Once it is defined, the operator is used in the same way as the built-in ones: you write the stream and the value on either side of the operator, and the stream carries the textual form of the value to its destination. Because the stream classes are left alone, your new operator sits alongside the existing ones and extends what the stream can handle.

Pointuser-defined typeoperator<< for Pointfree function you writestd::ostreamstandard type, not modifiedtakes const referencewrites text to
Which piece is defined by you, and which piece is left unmodified? The user-defined type and its free operator function are yours; the std::ostream class is not changed.

The operator is responsible for turning the value into text and writing that text to the stream. The stream then delivers the text to its destination. The custom type's knowledge of its own representation is what makes the output meaningful, while the stream remains a general-purpose channel that does not need to know about points.

cpp
Output
Point: (3, 4)
Notes on the code
  • The operator takes the stream by reference and a Point by const reference
  • It returns the same stream so that further << operations can be chained
  • The stream itself is never changed; only a new overload is added

Following a Chained Output Statement

In a single statement, several values are often sent to the stream one after another. Each << takes the stream produced by the previous step and returns it for the next one. When one of those values is your custom type, the statement dispatches to your operator at exactly that point in the chain, and then control returns to the stream so the chain can continue.

Programstatement with a chain of<<operator<< for Pointyour functionstd::ostreamsame stream objectDestinationscreen or file1. pass the Point value2. write the text form3. deliver the text4. return the stream for the next <<
When the statement sends a Point between two strings, which function handles each step, and what object does each step hand to the next?

Read the chain from left to right. The object passed to the next operator is always the stream itself, so the text from your Point operator ends up between the text that came before it and the text that comes after it, in the order the statement lists them.

Formatting That Persists Across Outputs

Once a formatting choice has been made on a stream, it can change how the following values appear. Manipulators are the tools for making these choices. The source gives two rules about their reach: precision() specifications apply to all following floating-point output operations, and floating-point format specifications, such as the scientific format, also apply to all following floating-point output operations. In other words, these effects persist; they are not limited to the single value that follows the manipulator.

3.14159printed with defaultprecision3.1after setprecision(2)2.71828printed with defaultprecision2.7after setprecision(2)later outputlater output
After the precision is set to 2, which later floating-point values change their appearance?

What do you think happens?

Given setprecision(2) is applied once, what do the two floating-point values print as in this single statement?

  • 3.14159 and 2.71828

  • 3.1 and 2.7

  • 3.14 and 2.72

  • Only the first value changes

Reveal answer
Answer:

3.1 and 2.7

The precision setting applies to every following floating-point output operation, so both values are shown with the requested precision of two significant digits. The effect is not limited to the first value.

Standard manipulators come from specific headers. When you use standard manipulators in general, include the ios header. When a manipulator takes an argument, such as a number of digits, include the iomanip header as well. Setting a precision with a value therefore needs iomanip, which is the header to remember whenever a manipulator is given a value.

Mistakes Beginners Make With Streams

  • Common mistake

    Modifying istream or ostream to make a custom type print

    Why it fails

    You do not need to modify the standard stream classes to add new input and output operators for your own types.

    Do this instead

    Write a free operator<< that takes std::ostream& and your type, and leave the standard classes alone.

  • Common mistake

    Assuming a precision setting only affects the next number

    Why it fails

    precision() and floating-point format specifications apply to all following floating-point output operations, so their effects persist.

    Do this instead

    Set the precision deliberately, and reset it explicitly when later output needs a different format.

  • Common mistake

    Using setprecision without including iomanip

    Why it fails

    A manipulator that takes an argument needs the iomanip header.

    Do this instead

    Include iomanip for setprecision and other manipulators that take arguments, and include ios for standard manipulators in general.

  • Common mistake

    Writing to a file stream that is not attached to a file

    Why it fails

    A stream that is not attached to a file cannot deliver the output you expect, so work is sent to nowhere.

    Do this instead

    Confirm that the file stream is attached to a file before you use it.

  • Common mistake

    Trying to copy a file stream

    Why it fails

    The source lists copying a file stream as a practice to avoid.

    Do this instead

    Use the file stream in place, passing it by reference, rather than duplicating it.

Safe Habits When Using File Streams

Best practice

Before you write to or read from a file stream, check that it is attached to a file. Then use the stream where it is declared instead of trying to copy it. Include ios for standard manipulators and iomanip for manipulators that take arguments, so that each formatting choice has the header it needs.

  1. 1
    Confirm the file stream is attached to a file.
  2. 2
    Pass the stream to functions by reference rather than copying it.
  3. 3
    Include ios for standard manipulators and iomanip for manipulators that take arguments.
  4. 4
    Set precision knowing it applies to all following floating-point output.
  5. 5
    Define << for your own type as a free function, leaving istream and ostream unchanged.

Checking Your Understanding

MEDIUM

A teammate adds a new operator<< for a Date type by editing the source of std::ostream. Explain why this is unnecessary, and describe the free function you would write instead, including the stream type it accepts and the value it returns.

Hints
  • Recall what the source says about modifying istream or ostream.
  • Think about how the chained statement needs the operator to hand the stream back.

Key Takeaways

  • User-defined types can define << and >> operators when their values have a meaningful textual representation, because the stream cannot know how a custom type should look as text.
  • You do not need to modify istream or ostream; a free operator function taking the stream by reference and returning it lets your type join the chain of output operations.
  • precision() and floating-point format specifications persist and apply to all following floating-point output operations.
  • Include ios for standard manipulators and iomanip for those that take arguments, and confirm a file stream is attached to a file without copying it.

Related concepts

More concepts starting with F