Columns


Standard C/C++

The Header <sstream>

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@lauger.com.

Introduction

The header <sstream> is a variation on the header (strstream>, which I have described in the previous two installments of this column. (See "Standard C/C++: The Header <strstream>," CUJ, January 1995, and "Standard C/C++: Implementing <strstream>," CUJ, February 1995.) It is designed to work more closely with class string. A string object controls an in-memory character sequence, supporting operations on the sequence such as assignment, concatenation, comparison, and searching.

Like <strstream>, <sstream) defines three classes that cooperate to help you read and write character sequences stored in memory:

If these classes sound suspiciously like the classes defined in the header <strstream), that is hardly an accident. A stringbuf object supports much the same control over input and output streams as does a strstreambuf object. There are just a few added capabilities:

Unlike a strstreambuf object, however, a stringbuf object does not let you muck with a character sequence that is also controlled by a string object. It simply facilitates copying between two representations private to each object.

The classes istringstream and ostringstream behave much like istrstream and ostrstream. They construct a stringbuf object for you. They also provide member functions that access the special features of a stringbuf object. In this case, that mostly involves copying to and from string objects, as described above.

A few subtle differences exist between class stringbuf and class strstreambuf. If an object of the latter class defines an output sequence, it always defines an input sequence as well. But that is not always true for an object of class stringbuf. Otherwise, the two kinds of stream buffers are more alike than different.

Recent Changes

As I emphasized last month, the code I present here is based on the draft C++ Standard as of March 1994. Despite the many changes that have occurred in the past year, the functionality of the classes defined in <sstream) is essentially unchanged — at least for the char-based streams we all know and love. But many headers have now been "templatized." In particular, iostreams operations are now mostly defined for an arbitrary "character" type T.

To reproduce the existing functionality, the library instantiates these general templates for T defined as type char. The net result is much the same, but the machinery — and the notation — is now far more elaborate. I prefer to show the behavior of iostreams for the more familiar char-based streams.

The library also instantiates these templates for T defined as type wchar_t. If you want to manipulate wide-character strings, that can be invaluable, but such usage is still relatively rare. Even more rare is the use of iostreams based on arbitrary characters of type T. (As far as I know, such usage is nonexistent.) It will be interesting to see how useful such generality will prove in future practice.

I also reported last month that the header <strstream) has been exempted from the templatizing treatment. The committee is staking the future on <sstream>, which does much the same thing as <strstream) but works with templatized strings of arbitrary character types. The older header <strstream> is retained mostly in the interest of preserving existing code.

Finally, I note that the member function name str has already been changed in class string since March 1994. It is now data. That cleanup has yet to extend to the classes defined in <sstream>, but it might.

Using <sstream>

You include the header <sstream> to make use of any of the classes istringstream ostringstream or stringbuf. Objects of these classes let you read and write in-memory character sequences just as if they were conventional files, and copy character sequences. You can choose among three patterns of access:

I deal with each of these options in turn. For a discussion of stream-positioning operations on in-memory character sequences, see the January 1995 installment. The issues are essentially the same.

If all you want to do is read an inmemory character sequence that is initialized from a string object, construct an object of class istringstream. If you know at construction time what string object s you wish to use, you can write:

istringstream strin(s);
The resultant stream buffer (pointed at by strin.rdbuf()) does not support insertions. You can, however, replace the character sequence completely from another string object s2 with the call:

strin.str(s2);
The stream position is reset to the beginning of the stream. (And the resultant stream buffer still does not support insertions.)

You can also construct an istringstream object with an empty character sequence, using the default constructor. Presumably, you would later supply a non-empty character sequence, as in:

istringstream strin;
strin.str(s);
If all you want to do is create an in-memory character sequence to be eventually copied to a string object, construct an object of class ostringstream to control insertions into it. You can write:

ostringstream 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.) The resultant stream buffer (pointed at by strout. rdbuf()) does not support extractions, by the way.

Your goal in creating a write-only character sequence is to capture the final result in a string object, as a rule. Write s = strout.str() to construct a string object, initialize it to control a copy of the character sequence, and assign it to the string object s. The two character sequences can, of course, evolve separately thereafter.

If you want a null-terminated string, there is no real need to insert a null character last. It will not be supplied for you when you call s = strout.str(), as above. On the other hand, you must then call s.c_str() to get a pointer to the beginning of the character sequence. That call will supply a terminating null character.

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. The classes istringstream and ostringstream are highly symmetric. Thus, you have three equally valid ways to do the job. If you don't want to supply an initial character sequence, you can write:

istringstream istr(ios::in | ios::out);
ostream ostr(istr. rdbuf());
or:

ostringstream ostr(ios::in | ios::out);
istream istr(ostr. rdbuf());
or:

stringbuf sb(ios::in | ios::out);
istream istr(&sb);
ostream ostr(&sb);
All approaches cause istr to control the input stream and ostr to control the output stream.

You can also supply an initial character sequence from a string object s in each of these three cases:

istringstream istr(s, ios::in | ios::out);
ostream ostr(istr. rdbuf( ) );
or:

ostringstream ostr(s, ios::in | ios::out);
istream istr(ostr. rdbuf() );
or:

stringbuf sb(s, ios::in | ios::out);
istream istr(&sb);
ostream ostr(&sb);
Note that both the input and output stream positions are initially at the beginning of the character sequence. That may not be what you intend. Always consider whether you want to alter the output stream position before you do anything else with such a read/write stream.

Implementing <sstream>

Listing 1 shows the file sstream, which implements the standard header <sstream>. It defines the classes stringbuf, istringstream, and ostringstream. Note that class stringbuf is based on class strstreambuf, which I described last month. The draft C++ Standard says that stringbuf is derived directly from streambuf. Such indirect derivation is permitted by the library "front matter."

I chose this implementation, as I hinted last month, because the two derived classes are so much alike. I added a bit of logic to class strstreambuf to close the gap:

I also added to class stringbuf the secret protected member function _Mode. It maps constructor arguments of type ios::openmode to their corresponding strstreambuf::_Strmode values:

The effect of all this groundwork is to dramatically reduce the amount of new code required to implement the classes defined in <sstream>.

Listing 2 shows the file stringbu. c, which defines the two functions required for practically any use of class stringbuf. These are the destructor and the member function stringbuf::_Mode. Class stringbuf has only two additional member functions not defined inline, the two flavors of str.

Listing 3 shows the file strbstr0. c, which defines the member function stringbuf::str(). If an input stream exists, the complexity lies in determining the current extent of the character sequence. The calculation is reminiscent of the logic for updating the strstreambuf member object _Seekhigh. (See last month.)

Listing 4 shows the file strbstr1.c, which defines the member function stringbuf::str(const string&). It discards any existing character sequence and reinitializes the stream buffer to control a copy of the new one. The strstreambuf secret member functions really pay off here.

The remaining source files implement the other two classes defined in <sstream>.

Listing 5 shows the file istrings. c, which defines the destructor for class istringstream. And Listing 6 shows the file ostrings. c, which defines the destructor for class ostringstream. The header supplies inline definitions for all other member functions in these two classes.

Testing <sstream>

Listing 7 shows the file tsstream. c. It tests the basic properties of the classes defined in <sstream>. It does so in three groups:

The function main() simply performs these groups of tests in the order shown. If all goes well, the program prints:

SUCCESS testing <sstream>
and takes a normal exit.

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