C/C++ Contributing Editors


Standard C/C++: Simple Iostreams

P. J. Plauger

Iostreams has long been a simple and extensible mechanism in every C++ library. In Standard C++ it's far more extensible, but not nearly so simple.


Long before there was a Standard C++ library there was iostreams. In fact, the earliest implementations of C++ contained little else in the way of a library. Maybe you'd find a string class, or a complex arithmetic class, or some sort of support for multithreading. Otherwise, you had to look to the underlying C library for functionality that was constant across multiple implementations.

The iostreams classes have served as a kind of existence proof, or proof of concept, for the basic features of C++. Operator overloading, strong typing, and manipulators all demonstrate their worth with such simple output expressions as:

cout << "hello, world" << endl;

Sure, you can do much the same thing by calling printf, but not with the same convenient notation, compile-time checking, or extensibility to types you define yourself.

When multiple inheritance found its way into C++, it was again the iostreams classes that showed what you could do with it. Consider the eponymous class iostream:

class iostream
    : public istream, ostream
    {.....}

It demonstrates how you can combine the behaviors of both formatted input streams and formatted output streams, to control a stream that can be both read and written. Less obviously, class iostream demonstrates the use of a virtual base class. Both class istream and class ostream have a common virtual base ios, so class iostream inherits a single member object of class ios, and thus stores just one set of format flags and related information. (Whether or not this is a good idea is another matter, but it does show a credible use for multiple inheritance.)

Most uses of iostreams are fairly simple. You insert text into an output stream, typically the standard output stream cout, by writing insertion expressions such as the one I showed above. With numeric inserters, it is easy enough also to format integer and floating-point values, and intermix them with literal text. Maybe you indulge in some formatting tricks by fiddling with the width value or the fill character stored in the object cout, as in:

cout << setw(20) << setfill('*')
     << total;

But that's typically the extent to which most programs stress the iostreams output facilities.

If you're rather more ambitious, you might also parse a bit of input by extracting text from cin, as in:

cin >> value;

But that's even less rare, given the varied requirements for formatting input and recovering from errors.

The iostreams classes changed considerably in the process of being standardized. They got "templatized," so that traditional single-byte and the newer wide-character streams derive from the same code. You can even define your own element type, with rules for how to map it to and from a stream of bytes in external files, and specialize the iostreams templates to deal with such creatures.

The saving grace is that conventional use of iostreams remains much the same, at least from the outside looking in. You still have the standard streams cin, cout, cerr, and clog. You can still insert and extract objects of all the basic types. You can still play cute games with manipulators such as endl and noskipws. As a result, most programs can be converted to work with the Standard C++ library with just a few changes at the top of the program. The old standby:

#include <iostream.h>

now becomes:

#include <iostream>
using namespace std;

and the rest of the code will probably survive the transition unchanged.

More sophisticated coding techniques fared less well, however.

Extending Iostreams

Iostreams was designed from early on as an extensible mechanism. Three workhorse classes partition the basic functionality:

You derive from these three workhorses to make useful sets of classes. Two examples with a long history are the set that performs file input/output and the set that performs in-memory buffering.

In the first case, class ifstream, derived from istream, lets you open a file by name for reading. It creates an object of class filebuf, derived from streambuf, to perform the actual reads (and backspacing, and file positioning) for the file. Class ofstream, derived from ostream, lets you open a file by name for writing. It creates an object of class filebuf to perform the actual writes (and file positioning) for the file. And, of course, class fstream, derived from iostream, combines the properties of both ifstream and ofstream.

Similarly, class istrstream, derived from istream, lets you initialize an in-memory buffer and read its contents just as if it were an input file. It creates an object of class strstreambuf, derived from streambuf, to perform the actual reads from the buffer. Class ostrstream, derived from ostream, lets you create an in-memory buffer. It creates an object of class strstreambuf to perform the actual writes and to extend the buffer as needed. And class strstream, derived from iostream, lets you read and write the in-memory buffer at the same time.

Given source code for these two examples, plus the basic workhorse classes from which they derive, you could pretty easily figure out how to roll your own. For many years, in fact, that's about all you had by way of guidance if you wanted to derive your own set of iostreams classes. Eventually, a book appeared that endeavored to capture the lore of working with iostreams classes. (See Teale [1] in the References below.) But by the time it appeared, the C++ Standards committee was well along the way to rendering this book obsolete, except as a historical guide to pre-standard implementations of C++ iostreams.

I got actively involved with the C++ standardization effort in early 1992, after I returned from my one-year sojourn in Australia. At that point, the committee was pretty determined to maintain binary compatibility with earlier versions of iostreams. Effectively, this requirement prohibited the addition of any virtual member functions to the existing iostreams classes. It also prohibited any semantic changes that would require changes in the values of existing enumeration constants. All in all, it added up to a pretty stringent constraint — too stringent to maintain, in fact. As I recall, compromises began to infiltrate the description sometime in 1993. But by the end of that year, it looked like the "new improved" iostreams had settled down, and the C++ Standard was about to freeze.

I essayed my own description of iostreams, and how to use it, in a book I published in mid 1994. (See Plauger [2] in the References below.) But even before the book went to press, the dam had burst. The wholesale addition of templates, as I described above, changed the appearance of the iostreams classes almost beyond recognition. The underlying functionality that I described in my book remains largely unchanged to this day. But the apparent usability of the book was severely compromised. For many practical purposes, the description was obsolete almost from the day of publication.

I think it is fair to say that there are few written guidelines for using the iostreams classes. Lest you retain any doubt, let me tell you what stimulated this column. A couple of weeks ago, I got a bug report from a fellow book author (who shall remain nameless, for reasons you will soon see). The author complained that a simple set of classes derived from the basic iostreams workhorses failed to behave properly when compiled with the Microsoft Visual C++ V6.0. Since I am the perpetrator of that library, it was only natural for the author to turn to me for a fix.

As it turned out, however, there was nothing to fix. This learned author had simply failed to grasp some of the basic changes that had occurred since the early days of iostreams, in the process of C++ standardization. Once I pointed out the oversights, the author was astute enough to fill in the blanks and get the set of classes working properly. But the incident gave me pause. If a leading expert in the field is confused, how does that bode for working programmers? The kind of people who read this column seldom want to become experts in the subtleties of Standard C++. (Here, I'm guessing about your motivations, Gentle Reader, but from a firm basis in experience.) No, those people want to learn just enough lore to know where to look, should they need to use some abstruse corner of the Standard C++ library. And they want to have a finite chance of succeeding when they try.

A Simple Example

The result of that encounter is the set of classes shown in Listing 1. They constitute not quite the simplest set of example classes I could contrive to illustrate how to derive from classes istream, ostream, iostream, and streambuf to do something new and moderately interesting.

The new and moderately interesting thing here is to perform input and output using just the basic functions getchar and putchar. You will find these declared in the Standard C header <stdio.h>, of course. They are the oldest of functions for performing single-character input and output, dating back to the earliest days of C and Unix. They are also the traditional primitives used in an embedded system, or some other context where input and output is only minimally supported. Often you will find that getchar reads a single character from a UART, and putchar writes a single character to the same chip. With that minimalist hook, you can do some pretty useful debugging on even the most primitive of embedded systems.

The action begins with the class simple_filebuf, derived as you might expect from streambuf. Note, by the way, that streambuf is no longer a simple class. Instead, it has become a typedef for the template specialization:

basic_streambuf<char,
    char_traits<char> >

I mention this as a justification for all the ugly notation I carefully (and unnecessarily) preserved in the code that follows. The iostreams template classes each rely on a template parameter to capture all the behavior peculiar to a given stream element. The end-of-file code is given by traits_type::eof(), which might not be the good old EOF you know and love. Similarly, an element can take two forms:

To be dead honest, you're supposed to call still other "traits" functions to compare elements, copy them, and convert them back and forth between the two forms.

I preserved all this elaborate machinery in the off chance that you might want to upgrade the classes to template classes. In that case, it's neither safe nor wise to stick in the actual types that you think are being used. Better to stick with the extensive parameterization that comes with templatized iostreams. Lest you worry about loss of performance, however, rest assured that most of these ugly expressions are inlined to the simple expressions you know and love from the early days of C and C++.

I also preserved the virtual destructors, mostly as placeholders. They're there to remind you to free any resources that you allocate when you define your own set of classes.

Class simple_filebuf can maintain an in-memory buffer, but it doesn't have to. You can allocate a buffer in the derived class, set up "get" pointers (for reading the buffer) by calling streambuf::setg, and set up "put" pointers (for writing the buffer) by calling streambuf::setp. But my goal here is simplicity. All those in-memory buffer pointers start out null. If they remain that way, all stream buffer operations should result in calls to the protected virtual member functions that do all the actual work.

In the early days of iostreams, you could get away with defining just two of these virtuals:

But life is rather less simple now. The C++ committee, for a variety of reasons too ornate to discuss here and now, added another protected virtual member function:

You can't really implement this function with just a call to getchar, since that primitive function also consumes the input character. If you're really using the Standard C library, you can call ungetc(ch, stdin), but that's rather more demanding on the underlying primitives than I intended with this simple example.

What I did instead was provide an override for uflow, which makes use of a single-character buffer stored in the simple_filebuf object. It puts back a character by calling yet another protected virtual member function:

As it turns out, you really want to supply a reasonable version of pbackfail anyway. Extractors that parse numeric input fields, for example, need to perform one character of lookahead to determine the end of the field. They all assume they can push back the last character read, for later consumption. If you don't supply a version of pbackfail, then you'd better maintain an in-memory buffer. One way or the other, the ability to back up over the last character read had better be present.

Conclusion

I tacked on a small test program, just to verify that all the classes behave pretty much as advertised. I compiled the code with Microsoft VC++ V6.0.

That's about it. You can use the example in Listing 1 as a starting point for a more elaborate set of classes you derive. You can also study the far more elaborate examples shipped with the compiler. See the headers <fstream>, <sstream>, and <strstream>. But if you look at those, be prepared to field quite a few distractions. They serve many masters.

References

[1] Steve Teale. C++ IOStreams (Addison-Wesley, 1993).

[2] P.J. Plauger. The Draft Standard C++ Library (Prentice Hall, 1995).

P.J. Plauger is Senior Editor of C/C++ Users Journal and President of Dinkumware, Ltd. He is the author of the Standard C++ Library shipped with Microsoft's Visual C++, v5.0. For eight years, he served as convener of the ISO C standards committee, WG14. He remains active on the C++ committee, J16. His latest books are The Draft Standard C++ Library, Programming on Purpose (three volumes), and Standard C (with Jim Brodie), all published by Prentice-Hall. You can reach him at pjp@plauger.com.