Skip to content

8.1. The IO Classes

Fundamental

The 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 strings.

Table 8.1. IO Library Types and Headers

HeaderType
iostreamistream and wistream read from a stream.
ostream and wostream write to a stream.
iostream and wiostream read and write a stream.
fstreamifstream and wifstream read from a file.
ofstream and wofstream write to a file.
fstream and wfstream read and write a file.
sstreamistringstream 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

Fundamental

As we saw in § 7.1.3 (p. 261), we cannot copy or assign objects of the IO types:

c++
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

CodeDescription
strm::iostatestrm 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::badbitstrm::iostate value used to indicate that a stream is corrupted.
strm::failbitstrm::iostate value used to indicate that an IO operation failed.
strm::eofbitstrm::iostate value used to indicate that a stream hit end-of-file.
strm::goodbitstrm::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:

c++
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:

c++
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:

c++
// 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:

c++
// 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?

c++
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

c++
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 from main.
  • 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 endl1.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 for cerr, so that writes to cerr 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 and cerr are both tied to cout. Hence, reading cin or writing to cerr flushes the buffer in cout.
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:

c++
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:

c++
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

c++
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:

c++
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.