Columns


Standard C/C++

The Header <strstream>

P.J. Plauger


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

Special Notice — C++ Draft Public Review

The US public review of the draft C++ standard is scheduled to begin as early as April 1995. For detailed information on how to get a copy of the draft and how to submit comments, send a message to the reflector c++std-notify@research.att.com. — pjp

Introduction

The header <strstream> defines three classes that cooperate to help you read and write character sequences stored in memory (a.k.a. "string buffers"):

An in-memory character sequence is, in many ways, the simplest kind of stream buffer. The buffer maintained by the controlling streambuf object is not merely a window on some separate representation, like an external file. Rather, the buffer is the character sequence in its entirety.

Nevertheless, a strstreambuf object can control a variety of in-memory character sequences:

All these choices are reflected in a plethora of constructors for the three classes defined in <strstream>.

The Simple Choices

Despite all the options, three simple choices stand out. You can initialize an istrstream object to control a constant array of characters. You can then extract from its associated input stream, but you cannot insert to the output stream. The result is effectively an istream object where you dictate the stream contents purely within the program. Or you can initialize an ostrstream object to control a non-constant array of characters. You can then insert into its associated output stream to store into the array.

Your third simple choice is to initialize an ostrstream object to control an initially empty dynamic output stream that can grow on demand. The result is effectively an ostream object where you capture the stream contents purely within the program. You can then capture the final stream contents before the ostrstream object is destroyed. Class strstreambuf supplies several member functions to help you capture these contents. For an object x of class strstreambuf, here's what you can do:

Here is the obvious use for the manipulator ends, by the way. (See "Standard C/C++: The Header <ostream>," CUJ, September 1994.) It's a clear way to supply a null character at the end of an in-memory character sequence.

I end this brief introduction by mentioning the ghost at the banquet table. The Standard C library has long supported a similar capability. You can obtain formatted input from an in-memory character sequence by calling sscanf. You can write formatted output to an in-memory character sequence by calling sprintf or vsprintf. (All three functions are declared in <stdio.h>.) So how are these three classes any better? The answer is easy:

I believe these are ample reasons to cultivate a knowledge of the header <strstream>.

What the Draft Standard Says

Class strstreambuf is the first of several classes derived from streambuf. (I describe more in future installments, on the headers <sstream> and <fstream>.) What makes each such stream buffer unique is the way it overrides the virtual member functions in the base class. The exception classes exhibit uniqueness in a small way, by overriding the virtual member function do_raise. (See "Standard C: The Header <exception>," CUJ, February 1994.) But the stream buffers indulge in specialization big time.

The convention within the draft C++ Standard is to comment out virtual member functions in the derived class. Each such comment is labeled inherited. The suggestion is that an implementation need not provide an overriding definition if the definition in the base class does the job.

For the classes derived from streambuf, however, these comments are often misleading. Listing 1, for example, shows how the draft C++ Standard defines the class strstreambuf. Several critical virtual member functions must have overriding definitions to satisfy the semantic requirements of the class. (The descriptions of these functions in the draft C++ Standard are often hard to read as well. They represent standardese at its legalistic extreme. But those descriptions are also the definitive word on how a class behaves in peculiar circumstances. Turn to them when tutorials prove inadequate.)

Listing 2 shows how the draft C++ Standard defines the class istrstream, and Listing 3 shows how it defines the class ostrstream. These are derived from the classes istream and ostream, respectively, to facilitate operations with stream buffers of class strstreambuf.

Using the Header

You include the header <strstream) to make use of any of the classes istrstream, ostrstream, or strstreambuf. Objects of these classes let you read and write in-memory character sequences just as if they were conventional files. You can choose among four patterns of access:

I deal with each of these options in turn.

Read-Only String Buffers

If all you want to do is read an in-memory character sequence, construct an object of class istrstream to specify the character sequence and control extractions from it. For a null-terminated string s, you can write:

   istrstream strin(s);
The character sequence begins at s and continues up to, but not including, the terminating null character that follows.

To control an array of n characters s with arbitrary values, you can write:

   istrstream strin(s, n);
In either case, s can be either a pointer to char or a pointer to const char. Whichever way you construct the istrstream object, the resultant stream buffer (pointed at by strin.rdbuf()) does not support insertions. You must specify an initial character sequence — class istrstream has no default constructor. The character sequence remains unchanged for the life of the istrstream object. (I suggest you refrain from altering the contents of the character sequence by other means, if that is possible, while the istrstream object controls accesses to it.)

Positioning within a read-only stream is fairly simple. A streamoff value is effectively an index into the character sequence. Thus:

   strin. rdbuf()->pubseekoff(0 ios::beg);
sets the stream position at the beginning (position zero) of the character sequence. If the length of the sequence is nonzero, the next character extracted will be the first character in the sequence. Any attempt to set the stream position to a negative value, or beyond the end of the character sequence, will fail. You can also call streambuf::pubseekpos, as above, but that is less necessary for an in-memory character sequence. Do so for code that shouldn't need to know what flavor stream buffer it is really dealing with. (For more discussion of stream positioning, see "Standard C: The Header <streambuf>," CUJ, June 1994.)

For what it's worth, you can also call strin.str() for a read-only character sequence. The call does not freeze the character sequence, since it is not dynamically alterable. The function merely returns a pointer to the beginning of the character sequence, just as for a character sequence you construct (as described below). This is always the beginning of the character sequence you specified when constructing the object.

If you do call strin.str() for a read-only character sequence as above, be warned. The call strin.rdbuf()->pcount() will not return the length of the sequence. Since no output stream exists, this call returns zero. You must determine the length of the sequence by some other means, such as positioning the stream at the end and inspecting the resultant stream offset.

Write-Only String Buffers

If all you want to do is create an in-memory character sequence, construct an object of class ostrstream to control insertions into it. You can write:

   ostrstream strout;
then insert into strout just like any other output stream. The character sequence can grow dynamically to arbitrary length. (The actual limit is usually INT_MAX, defined in <limits.h>, or when a storage allocation request fails.)

If you want a null-terminated string, be sure to insert a null character last, as with:

   strout << ends;
It will not be supplied for you when you call strout.str(). For a sequence of arbitrary characters, you can determine the number of characters you inserted by calling strout.pcount(). But see the warning below.)

Positioning within a write-only stream is also fairly simple. You can work mostly with streamoff values, as for read-only streams described above. A few caveats are in order, however:

As usual, my advice is to avoid such delicate areas in code you hope to keep portable. If you insist on pushing the limits of clearly defined behavior, expect surprises.

Your goal in creating a write-only character sequence is to capture the final result, as a rule. Call strout.str() to freeze the character sequence and return a pointer to its initial character. Remember that freezing the character sequence tells the strstreambuf destructor not to free storage for the character sequence. You must either free the storage yourself or be sure to call strout.freeze(0) before strout is destroyed.

Simple Read/Write String Buffers

If you want to create an in-memory character sequence that you can read as well as write, you need two objects to control the input and output streams. First, construct an object of class ostrstream to supply the strstreambuf object and control insertions, then construct an object of class istream to control extractions. You can write:

   ostrstream strout;
   istream strin(strout. rdbuf());
much as I have described several times in earlier installments for setting up a bidirectional stream. (See, for example, "Standard C: Introduction to Iostreams," CUJ, April 1994.) You can then insert into strout just like any other output stream. And once you have inserted characters, you can extract them from strin just like any other input stream.

A few caveats are in order, however. Try to extract beyond the last character in the sequence and you will encounter end-of-file. Once you extend the sequence by inserting characters, you can successfully extract again, but not until you clear eofbit if it is set in the istream object. Once again, this status bit proves less than trustworthy.

You can position the input and output streams separately or jointly. As usual, positioning operations demand a modicum of caution:

Note that the second caveat applies even to read-only or write-only in-memory character sequences.

As an important aside, here is a warning regarding the member function rdbuf. Several classes in the Standard C++ library are derived from either istream or ostream. These include istrstream and ostrstream, in this particular header. All such derived classes also provide a member function rdbuf() that hides the member function in the base class ios.

Why is this so? All such derived classes also provide a member object of a class derived from streambuf. In this header, that derived class happens to be strstreambuf. Consider, for example, the call istr.rdbuf(), for an object istr of class istrstream. It returns a pointer to the strstreambuf member object in istr. And the return type is indeed pointer to strstreambuf, not pointer to streambuf as in the base class ios.

A generic pointer to the base streambuf gets you to all the inherited member functions. It even gets you to the proper overriding definitions of virtual member functions. But to access any member functions peculiar to the derived class, you need such a specialized pointer. Again in this particular case, the member functions freeze, pcount, and str are peculiar to class strstreambuf, not its base class.

Potential confusion arises, however, once you make a call such as istr.rdbuf(strout.rdbuf()). The critical pointer in the ios subobject now designates a different stream buffer. But a subsequent call to istr.rdbuf() still returns a pointer to the member object within istr. To get the pointer actually used by inserters and extractors, you must make the hairy call ((ios&)istr).rdbuf().

Got all that? If not, don't worry too much about it. The bottom line is that you should follow a fundamental style rule. Alter the stored stream buffer pointer only in an object of class istream or ostream. Never do so in an object of a class derived from one of these. Even better, do as I did above — use a special istream or ostream object constructed from the outset to share an existing stream buffer. With either approach, there's never a second stream buffer lying around to cause confusion.

Sophisticated String Buffers

You can get even fancier with the classes defined in <strstream>. What follows is an assortment of more sophisticated setups.

Say you want to use an existing character array to store a character sequence, and you want to read the character sequence once you write it. If you declare:

   ostrstream strout(s, n);
   istream strin(strout.rdbuf());
then strout controls the sequence of n characters beginning at s. Both the read position and the write position are at the beginning of the character sequence. In this case, you cannot extend the sequence by inserting at or beyond character position n.

Here's a variation on this scenario. Say you want to use an existing character array to store a character sequence, and the array already stores a null-terminated string. You want to begin extracting from the beginning of the character sequence, but you want to begin inserting at the end of the null-terminated string (first replacing the terminating null). If you declare:

   ostrstream strout(s, n, ios::app);
   istream strin(strout.rdbuf());
then you get the desired behavior. Insertions effectively append characters to an initial null-terminated string, but won't continue past the end of the character array. For extractions, see the caveat above about the behavior of eofbit.

If you want to get fancier, you have to construct a strstreambuf object directly. This class has lots of constructors with lots of options. I describe here just the two that I feel are genuinely useful and safe.

Say you want to construct an in-memory character sequence that you know will get very large. You'd like to suggest, when constructing the strstreambuf object, that the object allocate space for a large sequence right off the mark. That can save lots of execution overhead in reallocating storage as the character sequence grows. Equally, you might want to construct a number of character sequences all of which will be fairly small. You'd like to suggest that each such object allocate only a small number of characters. That can save lots of unused storage. If you declare:

   strstreambuf sb(n);
   istream strin(&sb);
   ostream strout(&sb);
for some int value n, the constructor should take the hint. What it does with it is up to the implementation, but at least you get to make the suggestion.

Another approach to storage allocation is to do the job yourself. So your final interesting choice is to begin with two functions like:

#include <stdlib. h>

void *my_alloc(size_t n)
   {    // allocate n chars
   return (malloc(n));
   }

void my_free(void *p)
   {    // free allocated storage
   free(p);
   }
Tailor these basic functions as you see fit. Then you can declare:

   strstreambuf sb(&my_alloc,
                &my_free);
   istream strin(&sb);
   ostream strout(&sb);
The two functions you supply will be called, in place of new and delete expressions, to allocate and free storage for character sequences.

Next Month

That's a capsule summary of how to use string buffers, as specified by the draft C++ Standard. Next month, I'll show you one way to implement this header, and discuss some of the interesting challenges it presents.

This article is excerpted in part from P.J. Plauger, The Draft Standard C++ Library, (Englewood Cliffs, N.J.: Prentice-Hall, 1995.)