iostreams: Why so little information? Wednesday 20th June 2007
Most people’s exposure to C++ streams is with a “Hello, world” style program.
std::cout << "Hello, world!\n";
From there you move on to how you can “<<” different types and how you can produce the equivalent of something like the code below with only about 3 times as many characters.
printf("%08x : %.17g", uParm1, dParm2);
It’s not exactly a winning argument for using iostreams over printf and its friends.
So where is the power of iostreams? Well, stage two comes with the introduction of the ability to define your own insertion and extraction operations.
std::ostream& operator<<(std::ostream& , const MyClass&);
std::istream& operator>>(std::istream& , MyClass&);
The syntax isn’t trivial for beginners and, due to the way that the operators take a standard class on the left, you probably have to introduce the concept of friendship, but it’s pretty trivial.
So this is all well and good. We’ve created our own types and because we have defined the insertion and extraction operators in terms of the istream and ostream classes we can take advantage of writing to string, file, anything in fact!
Excellent, lets write an ostream that writes to a MyBucket class. By this I mean that I have a MyBucket class that can consume bytes and arranges them prettily in a plastic bucket. I’d like to wrap an ostream interface around MyBucket so that objects that have an operator<< onto and ostream can write into MyBucket. Should be easy, right?
So how do we do that?
This is where I found there to be a lack of readily available documentation how extend the iostream library in this way.
Naïvely, you might think that we have an inteface class, ostream, and we want to change its functionality so perhaps we should derive a class from it and override the functions we want to change. If you have a look at the documentation for ostream you’ll discover that there are virtually no virtual functions. It does have a virtual destructor. This means that if you pass it into a function like std::ostream& operator<<(std::ostream&, const SomeoneElsesClass&);
then you’re not going to be able to influence anything that happens inside this class from your derived ostream.
The “correct” answer is that you need to write a specialized std::streambuf. The streambuf interface abstracts the raw byte buffer functionality of files, strings, etc. from the [io]stream classes and allows the [io]stream class to perform all the formatting functionality that is what they are there for.
Once you have a class MyBucketStreamBuf : public std::streambuf { ...
you can then construct [io]streams around it an pass them around as any other [io]stream class. ostream (really std::basic_ostream
So why is there so little documentation on how to do anything more with iostreams than define your own insertion and extraction operations?
Because it’s C++ and you’re supposed to understand assembly language before you even start writing any code.
Surely reading some header files is trivial for anyone “qualified” to use C++?
Perhaps that’s what is wrong with the world. C++ isn’t taught correctly. How is C++ taught? More to the point, is it taught?
I was never taught it.
No-one I know was taught it, that I know of.
It is officially a “black art”.