Columns


Standard C

Introduction to lostreams

P.J. Plauger


P.J. Plauger is senior editor of The C Users Journal. He is convenor of the ISO C standards committee, WG14, and active on the C++ committee, WG21. His latest books are The Standard C Library, and Programming on Purpose (three volumes), all published by Prentice-Hall. You can reach him at pjp@plauger.com.

The largest single component of the Standard C++ library is the package called "iostreams." It consists of a whole slew of classes that work together to make input and output look simple. The commonest application is probably the classic:

#include <iostream>
.....
cout << "Hello, world." << endl;
which writes the message Hello, world. followed by a newline character to the standard output stream (same as stdout).

This simple expression is the tip of a rather large iceberg. Let's look a bit below the surface.

As you might guess, the header <iostream> declares all the necessary classes. (Existing implementations of C++ may append a .h, or a .hpp, to the header name.) The class we care about for this example is ostream, each of whose objects controls a stream into which you can insert characters. That stream can be an output file, a text string that grows dynamically in memory, or lots of other things.

In this particular case, cout is a static object of class ostream whose name has global linkage. It conspires rather intimately with stdout, the familiar Standard C library object of type pointer to FILE, to help you write formatted text to the standard output stream. You can freely intermix expressions such as the one above with more traditional calls such as to putchar or printf and get the result you'd expect.

(Translation: you don't have to worry about two different buffering mechanisms saving up characters and writing reordered blocks of characters to the standard output stream. Again, existing implementations don't always make this promise, at least not unless you perform some magic incantation at run time. And then you can often expect a degradation of performance. The draft C++ Standard hopes to encourage better synchronization of C and C++ I/O, if only to the standard streams.)

The header <iostream> declares two similar objects of class ostream:

Inserters

Now let's take a closer look at the first part of the example expression:

cout << "Hello, world." .....
The class ostream overloads the left-shift operator repeatedly with member functions to provide a rich set of inserters. These each encode a right-hand operand by various rules (akin to the conversion specifications for printf), then insert the encoded text in the stream controlled by the ostream object. In this particular case, the inserter called is:

ostream& operator<<(const char* s);
This function knows to write the null-terminated string pointed to by s to the output stream controlled by cout. Put another way, the function inserts each of the characters from the string into the controlled stream up to but not including the terminating null.

More complicated inserters turn binary integer and floating-point values into sequences of characters that human beings can read. For example, the inserter:

ostream& operator<<(int n);
lets you write expressions like:

cout << i;
that inserts, say, the sequence -123 in the controlled stream to represent the value —123 stored in the integer object i.

C programmers have been doing the same sort of thing for decades by writing:

printf("%s %d\n", s, i);
So what's the big deal? Well, the inserter approach offers a few advantages:

Manipulators

I won't elaborate much on that last point in this column. All you need to know for now is that inserting endl in an output stream has the effect of inserting a newline character ('\n') then flushing output to the controlled stream. It is but one of many interesting manipulators you can use with the inserter notation.

Other manipulators make up for one of the shortcomings of the inserter notation. Remember that with printf you can write some pretty fancy conversion specifications, such as:

printf("%+10d\n", i );
to force a plus sign on positive output and pad the generated text to (at least) ten characters. But operator<< takes only two operands. There is no place to smuggle in that extra formatting information.

The solution is to squirrel away the extra information in the cout object before performing the insertion. You can do this by calling member functions, as in:

cout.setf(showpos), cout.width(10);
cout << i;
Or you can use still more magic manipulators to achieve the same effect, as in:

cout << showpos << setw(10) << i;
As you can see, it's possible to have manipulators that take arguments. But that involves even more chicanery — a topic for much later in this series.

(In case you're wondering, the effect of showpos endures for subsequent insertions, in either of the above forms. I won't show how to turn it off here. But the field width evaporates after the first inserter that makes use of it.)

Extractors

You can probably guess what's coming next. The header <iostream> also supports reading and decoding text from various streams, including input files. It declares the object cin, which helps you extract characters from the standard input stream, in cooperation with stdin. As you might further guess, this object is of class istream, the obvious companion to ostream.

Thus, you can write code involving extractors, such as:

int n;
cin >> n;
to read a sequence of characters and decode them by the usual rules for encoded integer input. The "usual" rules are much as for the function scanf:

One small difference exists between inserters and extractors. You can insert the value of an expression into a stream. (This is usually called an "r-value" by old-line C programmers.) But you extract from a stream a value into an object. (Those same C programmers would call this an "l-value," but the times and the terms they are a-changing.) A corresponding difference appears in C — you call printf with arbitrary expressions for value arguments and you call scanf with pointer arguments to designate the objects to store into.

In C++, you declare extractors with reference arguments, as in:

istream& operator>>(int& n);
That ampersand lets you write a bald n, but still ensures that a real live 1-value gets bound to the corresponding parameter within the function. No worry about null pointers or other pointer type mismatches.

You can also play tricks with extractors, by the way, much like that end1 shorthand I showed earlier. If all you want to do, for example, is consume any pending white space from the standard input stream, you can write:

cin >> ws;
and the job is done. Similarly, you can communicate various bits of formatting information through other manipulators. much as with output streams. Once again, I won't begin to explain the magic behind that bald ws manipulator. Just note for now that such tricks are possible.

A Little History

These iostreams have several clear advantages over the formatted I/O functions of the Standard C library. Little wonder that every implementation of C++ has for years offered some version of iostreams, however much the implementation may vary in its support for other common library classes. Jerry Schwarz, now at Lucid Technology, gets credit for developing the earliest version of iostreams, for helping it become so widespread, and for seeing the package through several major revisions. He is also responsible for drafting the specification of iostreams in the draft standard C++ library.

Unfortunately, little has been written on the detailed architecture of iostreams. About the only commercially available guide is a book by Steve Teale [1], which deals with a slightly dated version of the package. Since the draft C++ standard progresses even beyond the current field version, Teale's book offers only limited guidance. Still, it's better than what you typically get from the vendor of a C++ translator.

For many class libraries this lack of information would not be a problem. But Schwarz designed iostreams to be extensible in several important ways:

Such power is not without its complexity. And complexity can be mastered safely only with careful guidance. To date, programmers have relied on access to bits and pieces of library source code to get that guidance. Where such code is not available, or where it varies among implementations, adequate guidance has been lacking. Standardizing iostreams is thus a major step toward helping the package realize its full potential.

Base Class ios

Let's begin with some basic architecture. Classes ostream and istream have several requirements in common:

To provide all these services, both classes derive from the virtual public base class ios.
Listing 1 shows how class ios is declared in the draft C++ Standard. Two kinds of members are commented out:

Note that class ios is a virtual base for both ostream and istream. That is more a matter of compatibility with past practice than of necessity. Some implementations control a stream that can be both read and written by declaring an object of class iostream, defined something like:

class iostream : public istream, ostream {..... };
Were the base ios not virtual, this class would end up with two such subobjects, not just one. That would lead to all sorts of confusion in trying to control the bidirectional stream. Making the base virtual does add a bit of complexity here and there, particularly with initialization, but it permits the traditional definition of class iostream for those who want to keep using it.

This class is not a part of the draft C++ standard, however, because it is no longer necessary. The preferred way to control a bidirectional stream is with two separate objects, one of class ostream and one of class istream. Both point to the same streambuf object, which is the only agent who really has to know that the stream can be both read and written. (That is part of the reason why an object of class ios contains a pointer to a separate streambuf object, instead of the object itself.)

In Times to Come

Class ios takes a lot of declaring, as you can see from Listing 1. For all that, it is neither a big nor a very complex class. Still, all that declaring demands a comparable amount of explaining. And class ios does lie at the heart of the entire iostreams class hierarchy. I plan to devote the next installment of this column to a careful study of this class, and the subtler implications of its semantics. Only after such a detailed introduction do classes ostream and istream begin to make sense.

Class streambuf is equally fundamental to iostreams. You can get quite a lot of use out of iostreams without ever declaring a streambuf object in anger. But if you want to know how the whole works hangs together, or if you want to extend iostreams in nontrivial ways, you must know how this class behaves in detail.

Manipulators are yet another topic. Many are simple and easy to explain once you know the basics of the classes ios, ostream, and istream. But all those manipulators with arguments derive from one of several "interesting" template classes. (In many existing implementations, they are built atop even more interesting macros.)

Two library classes show some of the power of class streambuf. Class strstreambuf provides capabilities very akin to the Standard C library function sprintf. You can use inserters and extractors to manipulate in-memory text strings. Class stringstreambuf is similar, except that it eases conversion between such in-memory strings and objects of the standard library class string. (I'll discuss class string in detail much later.)

Finally, we return to the objects that manipulate external files. Class filebuf lets you open files by name, much as with fopen, then manipulate them as iostreams. And the objects cin, cout, cerr, and clog have their own tale as well. It turns out that initializing these creatures, as required by the draft C++ Standard, is no mean feat.

The description of the iostreams facilities occupies about half the library portion of the draft C++ Standard. It's going to take quite some time to review it in adequate detail. Bear with me.

References

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