ostream-derived

By Genghis of Vampire Wars


In this article I will discuss how to create your own derivative of the Standard C++ Library's ostream type so that you can send formatted text to anywhere you like. The motivation of this was so that we could replace in Diku-derived Muds some rather ugly

    snprintf( buf, MAX_STRING_LENGTH, 
                  "You have %ld experience points.\n\r", 
                  ch->exp );
    send_to_char( buf, ch );
with the much more appealing
    ch << "You have " << ch->exp << "experience points." << endl;
No more string buffer overrun nightmares, no more two-stepping format-send, just turn the char_data struct into a class and get it to mimic the behaviour of cout. Elegant, n'est pas?

Beginner's wisdom would suggest that you need to overload the << operator in your char_data struct (soon-to-be class) for all the various data types you will encounter, but this is wrong and I'll explain why. Firstly unless you're really careful you will be unable to chain together many things into one line, negating the one feature that makes C++ streaming so attractive! Secondly, none of the various standard manipulators will work. These can be incredibly useful for fine-tuning the output and include things like endl, locales, base switching, etc. Last but not least, you're setting yourself up for a LOT of unnecessary hard work.

Somebody else has already done it all for you, it's sitting there in the Standard C++ Library waiting for you to inherit the appropriate classes and overload the key methods.

Why go to all the trouble of reinventing the wheel when you can bolt a standard wheel onto your very own cart?

std::streambuf

Every iostream has a stream buffer inside doing all the hard work. std::cout has a stream buffer that sends text to the console in a printf stylee, std::fstream's buffer performs file I/O operations, and std::stringstream's buffer accesses a standard std::string.

All these internal stream buffers are derived from a standard base class, std::streambuf. If we want to create a custom ostream class, we must first create a custom stream buffer of our own.

You need to write a class that inherits std::streambuf, and then overloads the necessary functions. In our case, the stream buffer will not be storing any data locally in a put area so we only need to change the behaviour of the overflow() method. The put area is useful for batching-up lots of text and sending it all off at once when the buffer is full. This is not what we want for network output because we need the user to get their text as soon as we send it. You should be able to copy the following snippet and put it directly into its own header file, e.g. netbuf.h

    #include <iostream>

    class netbuf : public std::streambuf
    {
    public:
        netbuf() : m_char(NULL) { setp(0,0); setg(0,0,0); }
        void set_char( char_data* character ) {m_char = character;}

    protected:
        virtual int overflow( int ch )
        {
            if (ch != std::EOF)
            {
                char buf[3] = {0,0,0};
                buf[0] = ch;
                if ( ch=='\n' )
                {
                    buf[0] = '\n';
                    buf[1] = '\r';
                }
                if (m_char)
                    send_to_char( buf, m_char );
            }
            return ch;
        }

    private:
        char_data* m_char;
    };
    

Note here that we are still using send_to_char() at the lowest level, nothing has changed in that respect. You can of course get the stream buffer to completely replace the send_to_char() / write_to_buffer() combination if you want, by allocating a put area and making the buffer flush itself when it encounters a newline. This would require overloading the behaviour of sync(), sputc(), and xsputn() in addition to overflow(). I will leave it as an exercise for the dedicated reader!

std::ostream

So we have our stream buffer, how do we get humble char_data to include it and behave like a c++ ostream? First step is to turn char_data into a c++ class. This is not part of the article, your design decisions on this matter will almost certainly differ from mine or anyone else's, so there is no point me going into detail. Let's just pretend that we have successfully managed to replace struct char_data{...}; with class char_data{...}; and proceed from there.

The next step is to derive our char_data class from std::ostream, turning

    class char_data
into
    class char_data : public std::ostream
Now we dynamically create an instance of our netbuf stream buffer in the char_data constructor:
    char_data() 
    : std::ostream(new netbuf)
    { 
        rdbuf()->set_char(this); 
    }
And destroy it again in the destructor, otherwise we'll leak a netbuf-sized piece of memory every time we deallocate a char_data object
    ~char_data()
    {
        delete rdbuf();
    }

And that's all you need to do. Honestly! char_data can now be treated as an ostream, so you are able to send all kinds of text into it, and the text will magically spit out on the player's terminal all nice and formatted just how you wanted it. Isn't technology great?




Genghis is a programmer for the Vampire Wars Mud server. If you have any questions or comments about this article, you can contact him at krug@krug.org.uk




This article was created with