PREAMBLE. The iostream library is an object-oriented library that provides input and output functionality using streams. The fundamental base classes in the iostream library are std::istream for streams that can only be read, and std::ostream for streams that can only be written. Reading from std::istream and writing to std::ostream, while handling all error states correctly, is challenging. In this article I’ll present code how it can be done.
Venue
A stream is an abstraction that represents a buffered device with an indefinte capacity of characters, on which input and output operations are performed, generally associated to a physical source or destination of characters, like a disk file, network socket, the keyboard, or the console. So the characters gotten or written to/from are input/output to a physical device.
Hierarchy
ios_base
^
| ifstream istringstream
| | |
ios +--------------+
^ |
| v
+--- istream <---+ +--- fstream
| | |
| |--- iostream <---+
| | |
+--- ostream <---+ +--- stringstream
^
|
+--------------+
| |
ofstream ostringstream
The header file <iostream> also declares some global objects that are used to perform basic input and output operations. They are divided into narrow-oriented objects for char's — cin, cout, cerr and clog — and their wide-oriented counterparts for wchar_t's.
These classes and objects are defined by the C++98 standard in namespace std.
Basic state bits
ios_base::iostate
Each stream object derived from ios contains a state object ios_base::iostate that sets four bits. These bits always and clearly define the current state of a stream object.
The type iostate is defined as public member in class ios_base, namespace std. It is inherited by classes istream and ostream. The actual form of this type is implemented-defined; it maybe an enum or a bitmask-type.
The state object can be accessed by the methods ios::rdstate (get bits) and ios::setstate (set bits) from each stream object. These methods are defined in class ios. The following example retrieves the state of cout:
// get state of stdout
#include <ostream>
using std::cout;
using std::ostream;
ostream::iostate state = cout.rdstate();
The four status bits are defined in class std::ios_base as individual constants. They can be accessed under their names
std::ios_base::badbit std::ios_base::failbit std::ios_base::eofbit std::ios_base::goodbit
The type of each identifier is std::ios_base::iostate. Maybe the confusing thing is that type iostate is implementation-defined, so each bit value typically is an enum or a static const iostate declaration.
ios_base::failbit
If a read/write error occured, failbit is set. failbit indicates logical errors that, by better programming, could be avoided. In contrast to badbit the stream object itself will be valid; other operations on the stream are still possible.
failbit is set by istream::operator>>(int&), for example, if the first character it encounters is not a valid digit.
Generally failbit is not set when EOF was reached. Exceptions are:
-
It is set by getline() when EOF is encountered without any character extracted
-
It is set if the line is too long for the stream’s buffer.
|
Note
|
There is no character value that can make getline() fail. |
Calling ios::clear() recovers the stream; see the [example] below.
ios_base::badbit
If there is an hardware failure, badbit is set. This is fatal: the stream object or buffer has lost its integrity and cannot be used anymore. The actual problem, however, remains unknown.
Consequently, the file may be incomplete or erronous, and the stream object shall not be used further. It is also considerable to throw a runtime_error.
badbit is never set when EOF was reached.
ios_base::eofbit
An input-operation has encountered the end of the stream. All input operations set eofbit when EOF is reached.
ostream's never set eofbit.
ios_base::goodbit
Returns true if and only if none of the three error state bits — eofbit, failbit or badbit — is set.
Methods
ios::eof()
Returns true if and only if the eofbit is set.
ios::bad()
Returns true if badbit is set.
ios::fail()
Returns true if either badbit or failbit is set.
ios::good()
Returns true if and only if bad(), fail() and eof() all return false.
So good() is not simply the opposite of bad(). Note that for stream S at any time, we may
assert(S.good() == (S.rdstate() == 0));
ios::rdstate()
Returns an object of type ios_base::iostate containing the four state bits, failbit, badbit, eofbit and goodbit.
ios::setstate()
Or’s in a state bit. The argument to this method is an object of type ios_base::iostate.
On a stream object S the method has the same effect as
S.clear(S.rdstate() | stateflag);
ios::clear()
Clears all state bits.
The argument to this method is an object of type ios_base::iostate, with the default argument ios_base::goodbit. So by default both error states and the EOF state bit are cleared.
Unlike with setstate, which or’s in bits, all state bits in the stream are replaced with the argument.
Operators
ios::operator void*()
The result of operator void* is equivalent to (bad() || fail()), in which case the result is NULL and the stream is bad.
Otherwise the operator returns a non-zero pointer, indicating the stream is not bad, but also maybe is at EOF.
|
Warning
|
The pointer value may not be dereferenced. It could be a dummy like 0xDEADBEEF. |
bool ios::operator!()
The boolean ios::operator! returns true if the stream is bad, equivalent to !(bad() || fail()).
Otherwise the stream is valid. It may also be at EOF.
Example: Test state bits
#include <iostream>
#include <fstream>
#include <cassert>
.
.
std::ifstream S;
S.open("/path/to/some/file");
if (!s) { // true
cerr << "Error opening file";
S.open("/path/to/some/otherfile");
if (void*(s) == 0)
cerr << "Error opening other file";
}
if (void*(s)) { // !NULL
// At this point we have a valid input stream; but when performing
// an output operation it fails. The fauxpas can be healed
// by calling clear() without an argument.
s << "bang!";
if (S.fail()) { // or use (!s)
S.clear();
assert(S.good());
}
}
Example: Test if a stream has been successfully and entirely read
// Reads a stream linewise and throws if not successful.
#include <iostream>
.
.
std::istream S;
.
.
const size_t max_line_length = 1000;
char *line_buffer = new char[max_line_length];
.
.
while (S.getline(line_buffer, max_line_length)) {
.
.
}
// The essence.
// Throw if the stream has not been entirely read.
// This means it is not at EOF and good.
if (S.bad()) {
// failed: stream may be incomplete or erronous
throw std::runtime_error("Bad state while reading/writing file; disk/network error?");
} else if (!S.eof() && S.fail()) {
// failed: read error / EOF not reached
throw std::logic_error("Fail state set while reading/writing file");
} else {
// ok, stream has been successfully and entirely read
}
Conclusion
-
A stream has four exact state bits.
-
A stream is good if not bad and not at EOF.
-
A stream is at EOF only if its eofbit is set.
-
A stream is bad if either its failbit or badbit is set; otherwise it is good or at EOF.
See also the getline example.
Thank you for your attention.