Jump to content


Ostream is easy


2 replies to this topic

#1 dave_

    Senior Member

  • Members
  • PipPipPipPip
  • 584 posts

Posted 13 September 2006 - 02:00 PM

When debugging a program I generally use a combination of breakpoints and logging. For convenience I use std::cout. It is an instance of std::ostream that redirects its input to the stdio console. std::ostream just provides operations to perform output operations on stream buffer.

Most of the time using this stream is sufficient, but sometimes, for example when working on a windows application, you don't have access to the console. Rather than change the code that relies on std::cout it is best to redirect its output. Luckily ostream has been designed to have its stream buffer changed. A simple approach is to redirect the output to a file. For example:


    // print to the console

    std::cout << "out to console" << std::endl;


    // open a file

    std::ofstream file("redirect.txt");


    // replace the buffer in cout, remember the old one.

    std::streambuf *old_buffer = std::cout.rdbuf(file.rdbuf());


    // log to cout which now redirects to the file

    std::cout << "to file" << std::endl;


    // and restore the old buffer

    std::cout.rdbuf(old_buffer);


    file.close();


Sometimes redirecting to a file is inconvient. To write to something else we need to create a new stream buffer. As I don't want to create to many dependencies and I'd like the streambuf to be a bit easier to use I've created a simple wrapper (its inline for brevity).

#include <streambuf>


class BufferedStringBuf : public std::streambuf

{

public:

    BufferedStringBuf(int bufferSize) 

    {

        if (bufferSize)

        {

            char *ptr = new char[bufferSize];

            setp(ptr, ptr + bufferSize);

        }

        else

            setp(0, 0);

    }

    virtual ~BufferedStringBuf() 

    {

        sync();

        delete[] pbase();

    }


    virtual void writeString(const std::string &str) = 0;


private:

    int	overflow(int c)

    {

        sync();


        if (c != EOF)

        {

            if (pbase() == epptr())

            {

                std::string temp;

                temp += char(c);

                writeString(temp);

            }

            else

                sputc(c);

        }


        return 0;

    }


    int	sync()

    {

        if (pbase() != pptr())

        {

            int len = int(pptr() - pbase());

            std::string temp(pbase(), len);

            writeString(temp);

            setp(pbase(), epptr());

        }

        return 0;

    }

};


This class creates buffer for storing streamed output. The class is abstract; virtual void writeString(const std::string &str) = 0; must be overriden. writeString should implement your output operation, this could write the text to an overlay on screen, perhaps some kind of console or whatever you want.

At the end of a line, or when the ostream receives a flush sync is called. This causes the current buffer to be passed as a string to writeString().

When this buffer is filled, the overridden function overflow is called. This causes the stream to flush and attempts to store the character that has overflowed.

The buffer is optional, when the bufferSize is zero it constantly overflows. This is a less than optimal way of operating as it has to output each character individually, but it doesn’t take any extra memory.

This wrapper has been written with simplicity in mind, it might not be the most robust, but it only depends on the STL and its pretty short.

So enough of the details, lets make it do stuff! Here are some examples:

#include "BufferedStringBuf.h"

#include <windows.h>


const int LineSize = 256;


class DebugBuf : public BufferedStringBuf

{

public:

    DebugBuf() : BufferedStringBuf(LineSize) {}


    virtual void writeString(const std::string &str)

    {

        OutputDebugString(str.c_str());

    }

};



The above class implements a log to visual studio's Output/Debug window.
To use it, take a similar approach to redirecting to a file.


    std::cout << "out to console" << std::endl;


    // replace the buffer

    DebugBuf debug_buffer;

    std::streambuf *old_buffer = std::cout.rdbuf(&debug_buffer);


    std::cout << "to visual studio debug output window" << std::endl;


    // restore the old buffer

    std::cout.rdbuf(old_buffer);


Here is another example buffer:

class MessageBoxBuf : public BufferedStringBuf

{

public:

    MessageBoxBuf() : BufferedStringBuf(LineSize) {}

    virtual void writeString(const std::string &str)

    {

        if ( str.size() > 1 ) // message box doesnt care about single characters

            MessageBox(NULL, str.c_str(), "Error", MB_OK|MB_ICONERROR);

    }

};


This one displays a message box when flushed.
Its probably not such a good one if you've got lots of text, but it just shows what can be done.

Finally, here is a useful buffer for chaining these buffers together.



class DupBuf : public BufferedStringBuf

{

public:


    DupBuf(std::ostream *stream1, std::ostream *stream2) :

      BufferedStringBuf(BufferSize), buffer1(stream1->rdbuf()), buffer2(stream2->rdbuf()) 

    {

    }

    

    virtual void writeString(const std::string &str)

    {

        const char *ptr = str.c_str();

        std::streamsize size = std::streamsize(str.size());


        buffer1->sputn(ptr, size);

        buffer2->sputn(ptr, size);

        buffer1->pubsync();

        buffer2->pubsync();

    }


private:


    std::streambuf *buffer1; 

    std::streambuf *buffer2;

};


This stores characters till the buffer is filled or flushed then it puts the characters in the other buffers and forces them to sync.
Used like this:


    DebugBuf debug_window_buf;

    std::ostream debug_stream(&debug_window_buf);


    DupBuf dup_buf(&std::cout, &debug_stream);

    old_buff = std::cout.rdbuf(&dup_buf);


    std::cout << "Print to debug output and stdio" << std::endl;


It will print to both the debug window and to std::cout. Again this is written for simplicity not performance.

Feel free to pick this code apart. I welcome constructive criticism.

Cheers

Dave


#2 .oisyn

    DevMaster Staff

  • Moderators
  • 1842 posts

Posted 13 September 2006 - 06:06 PM

You can also do this in applications that don't have a console:
AllocConsole();
freopen("CONOUT$", "w", stdout);

std::cout << "this string outputs to the newly created console window";
Of course, the downside is that it doesn't output to the outputwindow of VC++
C++ addict
-
Currently working on: the 3D engine for Tomb Raider.

#3 cypher543

    Member

  • Members
  • PipPip
  • 74 posts

Posted 13 September 2006 - 10:49 PM

This is cool. Very useful for a Quake-like console. I think I'll be using this in my next game project.





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users