|
|
Creating extractors for classes is similar to creating inserters. The Pair extractor could be defined thus:
istream& operator>>(istream& i, Pair& pair) { return i >> pair.x >> pair.y ; }By convention, an extractor converts characters from its first (istream&) argument, stores the result in its second (reference) argument, and returns its first argument. Making the second argument a reference is essential because the purpose of an extractor is to store a new value in the second argument.
A subtle point is the propagation of errors by extractors. By convention, an extractor whose first argument has a non-zero error state will not extract any more characters from the istream and will not clear bits in the error state, but it is allowed to set previously unset error bits. Further, an extractor that fails for some reason must set at least one error bit. The code in the Pair extractor does nothing explicitly to respect these conventions, but because the only way it modifies i is with extractors that honor the conventions, the conventions will be respected.
Conventions also apply to the meaning of the individual error bits. In particular ios::failbit indicates that some problem was encountered while getting characters from the ultimate producer, while ios::badbit means that the characters read from the stream did not conform to the expectation of the extractor. For example, suppose that the components of a Pair are supposed to be non-zero. The above definition might become:
istream& operator>>(istream& i, Pair& pair) { i >> pair.x >> pair.y ; if ( !i ) return i ; if ( pair.x == 0 || pair.y == 0 ) { i.clear(ios::badbit|i->rdstate()) ; } return i ; }This uses the (misleadingly named) clear() member function to set the error state to indicate that the extractor found incorrect data. Oring ios::badbit with i->rdstate() (the current state) preserves any bits that may previously have been set.
The Pair extractor has been defined so that it can input values that were output by the Pair inserter. Maintaining this symmetry is an important general principle that is worth some effort.
The next example is the Vec extractor, which will require an opening [ followed by a sequence of numbers, followed by a ]. Recall that the Vec inserter uses , as a separator and does not insert any whitespace between numbers. The extractor must accept such input. It will also accept slightly more general formats. In particular it allows extra whitespace, and it allows any visible character to be used as a separator. It also deals properly with a variety of special conditions such as errors in the input format.
istream& operator>>(iostream& i, Vec& v) { int n = 0 ; // number of elements char delim ;The steps this code performs are:v.resize(n) ;
// verify opening prefix i >> delim ; if ( delim != '[' ) ; i.putback(delim); i.clear(ios::badbit|i.rdstate()) ; return i ; } if ( i.flags() & ios::skipws ) i >> ws ; if ( i.peek() == ']' ) return i ;
// loop while ( i && delim != ']' ) { v.resize(++n) ; i >> v[n-1] >> delim ; }
If the next character is not [ (or if the state of the iostream already has error bits set), mark the state of i as bad, put delim back in e (where it may later be extracted again), and return. Putting delim back in the stream is not essential but it is consistent with the behavior of the predefined extractors.
Whether to skip is controlled by the ios::skipws flag set in a collection of bits known as i's format flags. This bit also controls skipping of whitespace in the predefined extractors. If it is set, whitespace was skipped before extracting the character stored into delim.
The next character is examined using the peek() member function. This returns the next character that would be extracted but leaves it in the stream.