TITLE: overloading the >> operator for user-defined classes (Newsgroups: comp.lang.c++.moderated, 16 July 97) HARRISON: jharrison@daa-utah.com (Joey Harrison) |> I'm designing a class lineBuffer that will do 3 things: |> - Extract a line of data from a file stream |> - Test the validity of the data |> - Allow the data to be extracted back out |> |> I have no problem with the first two operations. My problem comes when |> I |> try to overload the >> operator to allow extraction from lineBuffer. |> |> Here is what I have so far: |> |> class lineBuffer |> { |> private: |> strstream str; // is this the best choice of streams? |> |> public: |> lineBuffer() {} |> |> // this works fine |> friend istream& operator>>(istream &, genericSCRDataLine &); |> |> // this is trivial |> int isValid(); |> |> // this is the problem !!! |> friend ??????? operator>>(?????????, ??????????); |> } |> |> |> The implementation will execute as follows: |> |> void main(void) |> { |> float f1,f2,f3; |> ifstream fin("data.in"); |> lineBuffer line; |> |> fin >> line; |> |> if(line.isValid()) |> line >> f1 >> f2 >> f3; |> } |> |> I've tried several different ways to overload the >> operator, but I |> have had no success. |> Is there a better approach that I could take? KANZE: James Kanze Yes. Don't overload operator>>, but use the existing ones. Iostream's contains two distinct abstractions: formatting (istream and ostream), and a data sink and source (streambuf). In the first use in your example program ("fin >> line"), you are doing "formatted" input to the LineBuffer class; your use of the abstraction is perfectly correct. In the second use, however ("line >> ..."), the LineBuffer class is acting as a data source, not a formatter. The correct idiom here is to derive LineBuffer from streambuf, redefining the virtual function underflow, and initialize an istream using it, e.g.: int main() { float f1 , f2 , f3 ; ifstream fin( "data.in" ) ; LineBuffer line ; fin >> line ; if ( line.isValid() ) { istream input( &line ) ; input >> f1 >> f2 >> f3 ; } return 0 ; } Depending on the application, you might want to provide various additional "convenience" functions; an obvious idea would be a function LineBuffer::stream(), which returned a reference to an istream. (This would be an interesting turn-around, in which the streambuf class contains the istream which uses it.) With regards to the implementation of LineBuffer, what type you use to actually hold the data will at least partially depend on what your compiler supports. In theory, either string or stringbuf could be used; I'd probably go with string and its iterators, although maybe only because I'm more familiar with it. (The advantage of stringbuf is that you get the derivation from streambuf and the redefinition of underflow for free. On the other hand, I don't know if or how you can examine the characters in it for validation without extracting them.) In practice, these classes are part of the new standard, and as such, are often missing or not stable. If portability (including portability to the next release of your compiler) is an issue, you will probably want to use the deprecated class strstreambuf or your own string class instead. (An alternative solution would be to pick up a string class off the net, and use it. For this purpose, both g++'s string class and SGI's "rope" class are probably close enough to the standard to not require changes once your compiler implements whatever the final standard will require. The vector< char > could also be used without much change.) In any case, the class would look something like this: 1 class LineBuffer : public streambuf 2 { 3 public: 4 LineBuffer() ; 5 6 void inputLine( istream& ) ; 7 bool isValid() const ; 8 istream& stream() ; 9 10 virtual int underflow() ; 11 12 private: 13 string myLine ; 14 string::iterator myCurrentPosition ; 15 string::iterator myEndPosition ; 16 istream myStream ; 17 char myBuffer ; 18 } ; 19 20 LineBuffer::LineBuffer() 21 : myStream( this ) 22 { 23 } 24 25 void 26 LineBuffer::inputLine( istream& in ) 27 { 28 myLine.clear() ; 29 for ( int ch( in.get() ) ; 30 ch != EOF && ch != '\n' ; 31 ch = in.get() ) 32 myLine += static_cast< char >( ch ) ; 33 myCurrentPosition = myLine.begin() ; 34 myEndPosition = myLine.end() ; 35 myStream.clear() ; 36 setg( NULL , NULL , NULL ) ; 37 } 38 39 bool 40 LineBuffer::isValid() const 41 { 42 // your code... 43 } 44 45 int 46 LineBuffer::underflow() 47 { 48 int result( EOF ) ; 49 if ( gptr() < egptr() ) 50 result = static_cast< unsigned char >( *gptr() ) ; 51 else if ( myCurrentPosition != myEndPosition ) 52 { 53 result = 54 static_cast< unsigned char >( *myCurrentPosition ) ; 55 ++ myCurrentPosition ; 56 myBuffer = static_cast< char >( result ) ; 57 setg( &myBuffer , &myBuffer , &myBuffer + 1 ) ; 58 } 59 return result ; 60 } Notes: 17: Input MUST be buffered, so we provide a buffer. (In most implementations of string and vector< char >, the iterator myCurrentPosition could be used directly as the buffer. This is not guaranteed, however, and is not the case for the SGI rope class.) 21: I'm not really sure that this is 100% guaranteed to work. (We are passing a pointer to an uninitialized streambuf to the istream.) In practice, it works on all implementations I've seen, so it's "quasi-portable." (I'd like for the standards committee to bless it. I don't know if they want to, or if they have time, even if they wanted to.) 28: Clear out any previous line. I think that this function also exists in vector< char > and rope. I'm not too sure how you'd do this with strstreambuf or stringbuf. 32: In theory, I don't think the cast is necessary; the conversion should be implicit. However, string is, in fact, an instantiation of a template, and many compilers don't seem to like implicit conversions when mixed with templates. For that matter: if you know that there is a conversion, why not make it clear to the reader. Also: for vector< char > or rope (and hopefully the final version of string), the name of the function should be push_back, rather than operator+=. 35: This is to get rid of any previous error or end of file condition. 36: And this to clear out any unread character from the previous line. 46: Attention to the exact semantics of underflow. It does NOT remove a character from the input stream; it only ensures that the next character is in the buffer (and returns it). 50: If there is already a character in the buffer, just return it. The static_cast ensures that the value of the char is not negative. 53: Again, a static_cast is necessary to ensure that the value is not negative. 56: Finally, put the character into the buffer. (In this case, the cast is not necessary, but IMHO, it makes the code clearer.) Finally, you probably also want to redefine overflow and setbuf to return an error status, and sync to be a (successful) no-op, although in normal use, I would not expect any of these functions to be called. Also, just for the record: "void main" is illegal, and this probably belongs in comp.lang.c++.moderated, rather than comp.std.c++. (Although there are a few standards related issues: should string contain a function push_back? Should it be guaranteed that the constructor of istream doesn't actually use the streambuf passed to it in the constructor? And why are plain char's allowed to be signed, anyway?)