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.
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.
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.
Point: (3, 4)- 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.
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.
What do you think happens?
Given setprecision(2) is applied once, what do the two floating-point values print as in this single statement?
Reveal 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 failsYou do not need to modify the standard stream classes to add new input and output operators for your own types.
Do this insteadWrite 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 failsprecision()and floating-point format specifications apply to all following floating-point output operations, so their effects persist.Do this insteadSet the precision deliberately, and reset it explicitly when later output needs a different format.
Common mistake Using setprecision without including iomanip
Why it failsA manipulator that takes an argument needs the iomanip header.
Do this insteadInclude 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 failsA stream that is not attached to a file cannot deliver the output you expect, so work is sent to nowhere.
Do this insteadConfirm that the file stream is attached to a file before you use it.
Common mistake Trying to copy a file stream
Why it failsThe source lists copying a file stream as a practice to avoid.
Do this insteadUse the file stream in place, passing it by reference, rather than duplicating it.
Safe Habits When Using File Streams
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.
- 1Confirm the file stream is attached to a file.
- 2Pass the stream to functions by reference rather than copying it.
- 3Include ios for standard manipulators and iomanip for manipulators that take arguments.
- 4Set precision knowing it applies to all following floating-point output.
- 5Define << for your own type as a free function, leaving istream and ostream unchanged.
Checking Your Understanding
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.