8.1. The IO Classes
FundamentalThe IO types and objects that we’ve used so far manipulate char
data. By default these objects are connected to the user’s console window. Of course, real programs cannot be limited to doing IO solely to or from a console window. Programs often need to read or write named files. Moreover, it can be convenient to use IO operations to process the characters in a string
. Applications also may have to read and write languages that require wide-character support.
To support these different kinds of IO processing, the library defines a collection of IO types in addition to the istream
and ostream
types that we have already used. These types, which are listed in Table 8.1, are defined in three separate headers: iostream
defines the basic types used to read from and write to a stream, fstream
defines the types used to read and write named files, and sstream
defines the types used to read and write in-memory string
s.
Table 8.1. IO Library Types and Headers
Header | Type |
---|---|
iostream | istream and wistream read from a stream. ostream and wostream write to a stream. iostream and wiostream read and write a stream. |
fstream | ifstream and wifstream read from a file. ofstream and wofstream write to a file. fstream and wfstream read and write a file. |
sstream | istringstream and wistringstream read from a string. ostringstream and wostringstream write to a string. stringstream and wstringstream read and write a string. |
To support languages that use wide characters, the library defines a set of types and objects that manipulate wchar_t
data (§ 2.1.1, p. 32). The names of the wide-character versions begin with a w
. For example, wcin, wcout
, and wcerr
are the wide-character objects that correspond to cin, cout
, and cerr
, respectively. The wide-character types and objects are defined in the same header as the plain char
types. For example, the fstream
header defines both the ifstream
and wifstream
types.
Relationships among the IO Types
Conceptually, neither the kind of device nor the character size affects the IO operations we want to perform. For example, we’d like to use >>
to read data regardless of whether we’re reading a console window, a disk file, or a string
. Similarly, we’d like to use that operator regardless of whether the characters we read fit in a char
or require a wchar_t
.
The library lets us ignore the differences among these different kinds of streams by using inheritance. As with templates (§ 3.3, p. 96), we can use classes related by inheritance without understanding the details of how inheritance works. We’ll cover how C++ supports inheritance in Chapter 15 and in § 18.3 (p. 802).
Briefly, inheritance lets us say that a particular class inherits from another class. Ordinarily, we can use an object of an inherited class as if it were an object of the same type as the class from which it inherits.
The types ifstream
and istringstream
inherit from istream
. Thus, we can use objects of type ifstream
or istringstream
as if they were istream
objects. We can use objects of these types in the same ways as we have used cin
. For example, we can call getline
on an ifstream
or istringstream
object, and we can use the >>
to read data from an ifstream
or istringstream
. Similarly, the types ofstream
and ostringstream
inherit from ostream
. Therefore, we can use objects of these types in the same ways that we have used cout
.
INFO
Everything that we cover in the remainder of this section applies equally to plain streams, file streams, and string
streams and to the char
or wide-character stream versions.
8.1.1. No Copy or Assign for IO Objects
FundamentalAs we saw in § 7.1.3 (p. 261), we cannot copy or assign objects of the IO types:
ofstream out1, out2;
out1 = out2; // error: cannot assign stream objects
ofstream print(ofstream); // error: can't initialize the ofstream parameter
out2 = print(out2); // error: cannot copy stream objects
Because we can’t copy the IO types, we cannot have a parameter or return type that is one of the stream types (§ 6.2.1, p. 209). Functions that do IO typically pass and return the stream through references. Reading or writing an IO object changes its state, so the reference must not be const
.
8.1.2. Condition States
Inherent in doing IO is the fact that errors can occur. Some errors are recoverable; others occur deep within the system and are beyond the scope of a program to correct. The IO classes define functions and flags, listed in Table 8.2, that let us access and manipulate the condition state of a stream.
Table 8.2. IO Library Condition State
Code | Description |
---|---|
strm::iostate | strm is one of the IO types listed in Table 8.1 (p. 310). iostate is a machine-dependent integral type that represents the condition state of a stream. |
strm::badbit | strm::iostate value used to indicate that a stream is corrupted. |
strm::failbit | strm::iostate value used to indicate that an IO operation failed. |
strm::eofbit | strm::iostate value used to indicate that a stream hit end-of-file. |
strm::goodbit | strm::iostate value used to indicate that a stream is not in an error state. This value is guaranteed to be zero. |
s.eof() | true if eofbit in the stream s is set. |
s.fail() | true if failbit or badbit in the stream s is set. |
s.bad() | true if badbit in the stream s is set. |
s.good() | true if the stream s is in a valid state. |
s.clear() | Reset all condition values in the stream s to valid state. Returns void . |
s.clear(flags) | Reset the condition of s to flags . Type of flags is strm::iostate . Returns void . |
s.setstate(flags) | Adds specified condition(s) to s . Type of flags is strm::iostate . Returns void . |
s.rdstate() | Returns current condition of s as a strm::iostate value |
As an example of an IO error, consider the following code:
int ival;
cin >> ival;
If we enter Boo
on the standard input, the read will fail. The input operator expected to read an int
but got the character B
instead. As a result, cin
will be put in an error state. Similarly, cin
will be in an error state if we enter an end-of-file.
Once an error has occurred, subsequent IO operations on that stream will fail. We can read from or write to a stream only when it is in a non-error state. Because a stream might be in an error state, code ordinarily should check whether a stream is okay before attempting to use it. The easiest way to determine the state of a stream object is to use that object as a condition:
while (cin >> word)
// ok: read operation successful . . .
The while
condition checks the state of the stream returned from the >>
expression. If that input operation succeeds, the state remains valid and the condition will succeed.
Interrogating the State of a Stream
Using a stream as a condition tells us only whether the stream is valid. It does not tell us what happened. Sometimes we also need to know why the stream is invalid. For example, what we do after hitting end-of-file is likely to differ from what we’d do if we encounter an error on the IO device.
The IO library defines a machine-dependent integral type named iostate
that it uses to convey information about the state of a stream. This type is used as a collection of bits, in the same way that we used the quiz1
variable in § 4.8 (p. 154). The IO classes define four constexpr
values (§ 2.4.4, p. 65) of type iostate
that represent particular bit patterns. These values are used to indicate particular kinds of IO conditions. They can be used with the bitwise operators (§ 4.8, p. 152) to test or set multiple flags in one operation.
The badbit
indicates a system-level failure, such as an unrecoverable read or write error. It is usually not possible to use a stream once badbit
has been set. The failbit
is set after a recoverable error, such as reading a character when numeric data was expected. It is often possible to correct such problems and continue using the stream. Reaching end-of-file sets both eofbit
and failbit
. The goodbit
, which is guaranteed to have the value 0, indicates no failures on the stream. If any of badbit
, failbit
, or eofbit
are set, then a condition that evaluates that stream will fail.
The library also defines a set of functions to interrogate the state of these flags. The good
operation returns true
if none of the error bits is set. The bad
, fail
, and eof
operations return true
when the corresponding bit is on. In addition, fail
returns true
if bad
is set. By implication, the right way to determine the overall state of a stream is to use either good
or fail
. Indeed, the code that is executed when we use a stream as a condition is equivalent to calling !fail()
. The eof
and bad
operations reveal only whether those specific errors have occurred.
Managing the Condition State
The rdstate
member returns an iostate
value that corresponds to the current state of the stream. The setstate
operation turns on the given condition bit(s) to indicate that a problem occurred. The clear
member is overloaded (§ 6.4, p. 230): One version takes no arguments and a second version takes a single argument of type iostate
.
The version of clear
that takes no arguments turns off all the failure bits. After clear()
, a call to good
returns true
. We might use these members as follows:
// remember the current state of cin
auto old_state = cin.rdstate(); // remember the current state of cin
cin.clear(); // make cin valid
process_input(cin); // use cin
cin.setstate(old_state); // now reset cin to its old state
The version of clear
that takes an argument expects an iostate
value that represents the new state of the stream. To turn off a single condition, we use the rdstate
member and the bitwise operators to produce the desired new state.
For example, the following turns off failbit
and badbit
but leaves eofbit
untouched:
// turns off failbit and badbit but all other bits unchanged
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
INFO
Exercises Section 8.1.2
Exercise 8.1: Write a function that takes and returns an istream&
. The function should read the stream until it hits end-of-file. The function should print what it reads to the standard output. Reset the stream so that it is valid before returning the stream.
Exercise 8.2: Test your function by calling it, passing cin
as an argument.
Exercise 8.3: What causes the following while
to terminate?
while (cin >> i) /* ... */
8.1.3. Managing the Output Buffer
Each output stream manages a buffer, which it uses to hold the data that the program reads and writes. For example, when the following code is executed
os << "please enter a value: ";
the literal string might be printed immediately, or the operating system might store the data in a buffer to be printed later. Using a buffer allows the operating system to combine several output operations from our program into a single system-level write. Because writing to a device can be time-consuming, letting the operating system combine several output operations into a single write can provide an important performance boost.
There are several conditions that cause the buffer to be flushed—that is, to be written—to the actual output device or file:
- The program completes normally. All output buffers are flushed as part of the
return
frommain
. - At some indeterminate time, the buffer can become full, in which case it will be flushed before writing the next value.
- We can flush the buffer explicitly using a manipulator such as
endl
(§ 1.2, p. 7). - We can use the
unitbuf
manipulator to set the stream’s internal state to empty the buffer after each output operation. By default,unitbuf
is set forcerr
, so that writes tocerr
are flushed immediately. - An output stream might be tied to another stream. In this case, the buffer of the tied stream is flushed whenever the tied stream is read or written. By default,
cin
andcerr
are both tied tocout
. Hence, readingcin
or writing tocerr
flushes the buffer incout
.
Flushing the Output Buffer
Our programs have already used the endl
manipulator, which ends the current line and flushes the buffer. There are two other similar manipulators: flush
and ends. flush
flushes the stream but adds no characters to the output; ends
inserts a null character into the buffer and then flushes it:
cout << "hi!" << endl; // writes hi and a newline, then flushes the buffer
cout << "hi!" << flush; // writes hi, then flushes the buffer; adds no data
cout << "hi!" << ends; // writes hi and a null, then flushes the buffer
The unitbuf
Manipulator
If we want to flush after every output, we can use the unitbuf
manipulator. This manipulator tells the stream to do a flush
after every subsequent write. The nounitbuf
manipulator restores the stream to use normal, system-managed buffer flushing:
cout << unitbuf; // all writes will be flushed immediately
// any output is flushed immediately, no buffering
cout << nounitbuf; // returns to normal buffering
INFO
Caution: Buffers Are Not Flushed If the Program Crashes
Output buffers are not flushed if the program terminates abnormally. When a program crashes, it is likely that data the program wrote may be sitting in an output buffer waiting to be printed.
When you debug a program that has crashed, it is essential to make sure that any output you think should have been written was actually flushed. Countless hours of programmer time have been wasted tracking through code that appeared not to have executed when in fact the buffer had not been flushed and the output was pending when the program crashed.
Tying Input and Output Streams Together
When an input stream is tied to an output stream, any attempt to read the input stream will first flush the buffer associated with the output stream. The library ties cout
to cin
, so the statement
cin >> ival;
causes the buffer associated with cout
to be flushed.
INFO
Interactive systems usually should tie their input stream to their output stream. Doing so means that all output, which might include prompts to the user, will be written before attempting to read the input.
There are two overloaded (§ 6.4, p. 230) versions of tie:
One version takes no argument and returns a pointer to the output stream, if any, to which this object is currently tied. The function returns the null pointer if the stream is not tied.
The second version of tie
takes a pointer to an ostream
and ties itself to that ostream
. That is, x.tie(&o)
ties the stream x
to the output stream o
.
We can tie either an istream
or an ostream
object to another ostream
:
cin.tie(&cout); // illustration only: the library ties cin and cout for us
// old_tie points to the stream (if any) currently tied to cin
ostream *old_tie = cin.tie(nullptr); // cin is no longer tied
// ties cin and cerr; not a good idea because cin should be tied to cout
cin.tie(&cerr); // reading cin flushes cerr, not cout
cin.tie(old_tie); // reestablish normal tie between cin and cout
To tie a given stream to a new output stream, we pass tie
a pointer to the new stream. To untie the stream completely, we pass a null pointer. Each stream can be tied to at most one stream at a time. However, multiple streams can tie themselves to the same ostream
.