Archive

Archive for 2010/07/01

Extending std::ostream HOWTO

2010/07/01 1 comment

Extending std::ostream – HOWTO

iostream class design

First things to know.

  1. All basic_* classes are actually the template version of the usual classes. So it’s simpler to work with specialized classes for early development.
    That is instead of working with the class std::basic_ostream<<class _CharT, class _Traits> works with std::ostream.
    It’s actually just because of typedef basic_ostream<char, char_traits<char> >  ostream;
  2. Streams are there to hide actual implementation of communicating with the outside world, and separate communication from the logic of your application. These type of communication requires buffer for decent performance, and these are implemented in streambuf’s children classes. Decent STL implementations should provide you with stringbuf and filebuf as a minimum.

Therefore we could represent dataflows like this :

App ==>> ostream -->> ostreambuf --- ES
ES --- istreambuf -->> istream ==>> App

ES stands here for external systems, whether it is the OS, or any other
system you need to connect to without bothering about connection intricacies

Design choice

For simplicity sake, we will work while considering a very simple usecase :

MyOStreamBuf sb;
MyOstream mos(&sb);
int value = 42;
mos << "value :" << value << std::endl;

The simplest way to extend a stream usuallty is to extend streambuf with
“mystreambuf” and provide a stream extension “mystream” to work on it the same
way that ostream works on ostreambuf. This way the user of mystream will use it
as he could use std::ostream, and the implementation details will be hidden.

Streambuf Extension : What is needed ?

There is actually very little required to extend std::ostreambuf. Here is an
exemple with a nullstream implementation (template version), which does
nothing.

streambuf::overflow is called whenever the buffer is full and action needs to
be taken to enable more input. Depending on the kind of buffer we are
manipulating, this can mean flushing, extending buffer memory, etc.
It is expected that any class implementing streambuf will overload
streambuf::overflow(typename trais::int_type c)

Other usually overloaded functions, when writing streambuf implementations are streambuf protected virtual function. it is advised not to overload any public function, as they guaranty the proper behaviour of std::streambuf when used by a std::stream. In their specialized version :

  1. virtual std::streamsize xsputn ( const char * s, std::streamsize n ); is called whenever a character needs to be written into the std::streambuf via std::streambuf::sputn(), called from ostream& operator<<(ostream& , Type inst). Special formatting can be done here.
    The return status is expected to be n. If it is 0 the stream using that buffer will set ios::failbit to true, and prevent any more use of that stream.
  2. virtual int sync (); is called whenever the buffer needs to be syncrhonised with the ES, usually via std::ostreambuf::pubsync() called from std::ostream::flush() (called by manipulator std::endl for exemple)

On the use case :

(mos << "value :" ) // calls xsputn(), ( or overflow() if buffer is full)
<< value            //calls xsputn(),
//note the formatting from int to char* has been made by operator<<
<< std::endl;       //calls overflow() and then flush()

Stream Extension : Wrapping My streambuf extension

Here is an exemple with a nullstream implementation. Watch how the constructor needs to call init() on the buffer type, to bind the stream to it.

  1. An explicit constructor of stream can also be created and take as parameter the proper streambuf type. In the specialized version :
    explicit Mystream(Mystreambuf* lsb);
  2. It is good to overload also Mystreambuf* rdbuf() const to return the actual Mystreambuf type instead of the original std::streambuf
  3. For the same reason, you should also overload :
    //Put character (public member function)
    Mystream& put ( char c );
    //Write block of data (public member function)
    Mystream& write ( const char* s , std::streamsize n );
    //Flush output stream buffer (public member function)
    Mystream& flush ( );

Conclusion

You should now be able to write your own streambuf implementation, and a stream to allow the use of operator<< for all basic types, as well as all the standard manipulators defined in STL. And this without coding any operator<< function or manipulator.

std::ostreambuf is extendable quite simply. It is possible but more complicated to extend std::ostream, as it would probably require rewriting a lot of code. Therefore if an extended feature can be put into streambuf, it seems much simpler to do so.

This has been used in the AML project to write logstreambuf and logstream. At the time of writing the development of the Logging feature is still going on…

Categories: C++