9.6 Errors and Exceptions

By default, I/O streams do not raise exceptions for errors. Instead, each stream keeps a mask of error bits called the I/O state. The state mask keeps track of formatting failures, end-of-file conditions, and miscellaneous error conditions. The ios_base class template defines several member functions for testing and modifying the state flags (rdstate, setstate, fail, etc.).

A common idiom is to read from an input stream until an input operation fails. Because this idiom is so common, the standard library makes it easy. Instead of calling rdstate and testing the state explicitly, you can simply treat the stream object as a Boolean value: true means the state is good, and false means the state has an error condition. Most I/O functions return the stream object, which makes the test even easier:

while (cin.get(c))

  cout.put(c);

The basic_ios class overloads operator void* to return a non-null pointer if the state is good or a null pointer for any error condition. Similarly, it overloads operator! to return true for any error condition. (As explained later in this section, an end-of-file is not an error condition.) This latter test is often used in conditional statements:

if (! cout)

  throw("write error");

The state mask has three different error bits:

badbit

An unrecoverable error occurred. For example, an exception was thrown from a formatting facet, an I/O system call failed unexpectedly, and so on.

eofbit

An end-of-file upon input.

failbit

An I/O operation failed to produce any input or output. For example, when reading an integer, if the next input character is a letter, no characters can be read from the stream, which results in an input failure.

The basic_ios conditional operators define "failure" as when badbit or failbit is set, but not when eofbit is set. To understand why, consider the following canonical input pattern. During a normal program run, the input stream's state is initially zero. After reading the last item from the input stream, eofbit is set in the state. At this time, the state does not indicate "failure," so the program continues by processing the last input item. The next time it tries to read from the input stream, no characters are read (because eofbit is set), which causes the input to fail, so the stream sets failbit. Now a test of the input stream returns false, indicating failure, which exits the input loop.

Sometimes, instead of testing for failure after each I/O operation, you may want to simplify your code. You can assume that every operation succeeds and arrange for the stream to throw an exception for any failure. In addition to the state mask, every stream has an exception mask, in which the bits in the exception mask correspond to the bits in the state mask. When the state mask changes, if any bit is set in both masks, the stream throws an ios_base::failure exception.

For example, suppose you set the exception mask to failbit | badbit. Using the canonical input pattern, after reading the last item from the input stream, eofbit is set in the state. At this time, rdstate( ) & exceptions( ) is still 0, so the program continues. The next time the program tries to read from the input stream, no characters are read, which causes the input to fail, and the stream sets failbit. Now rdstate( ) & exceptions( ) returns a nonzero value, so the stream throws ios_base::failure.

A stream often relies on other objects (especially locale facets) to parse input or format output. If one of these other objects throws an exception, the stream catches the exception and sets badbit. If badbit is set in the exceptions( ) mask, the original exception is rethrown.

When testing for I/O success, be sure to test for badbit as a special indicator of a serious failure. A simple test for ! cin does not distinguish between different reasons for failure: eofbit | failbit might signal a normal end-of-file, but failbit | badbit might tell you that there is something seriously wrong with the input stream (e.g., a disk error). One possibility, therefore, is to set badbit in the exceptions( ) mask so normal control flow deals with the normal situation of reading an end-of-file. However, more serious errors result in exceptions, as shown in Example 9-10.

Example 9-10. Handling serious I/O errors
#include <algorithm>

#include <cstddef>

#include <exception>

#include <iostream>

#include <map>

#include <string>



void print(const std::pair<std::string, std::size_t>& count)

{

  std::cout << count.first << '\t' << count.second << '\n';

}



int main(  )

{

  using namespace std;



  try {

    string word;

    map<string, size_t> counts;

    cin.exceptions(ios_base::badbit);

    cout.exceptions(ios_base::badbit);

    while (cin >> word)

      ++counts[word];

    for_each(counts.begin(  ), counts.end(  ), print);

  } catch(ios_base::failure& ex) {

    std::cerr << "I/O error: " << ex.what(  ) << '\n';

    return 1;

  } catch(exception& ex) {

    std::cerr << "Fatal error: " << ex.what(  ) << '\n';

    return 2;

  } catch(...) {

    std::cerr << "Total disaster.\n";

    return 3;

  }

}