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; } }