Handy code snippet

Published March 05, 2008
Advertisement
The Problem
Often when working in C++ I find myself in a situation where I want to be able to quickly drop some formatted output in a specific way. My usual standby is to use a lot of intermediate stringstream objects, like this:

void OutputMessage(const std::wstring& message);void FooFunction(){   int foo = 4;   std::wstring bar = "Stuff!";   // Do some work on foo and bar   {      std::wostringstream msg;      msg << "State of foo is " << foo << " and bar is " << bar;      OutputMessage(msg.str());   }}


This allows any given kind of output function we want: a message box, output to a custom console in a game, logging to a file, sending over a network debug connection, and so on.

However, I find this unwieldy and annoying, especially when I have to dot my code with lots of inline stringstreams. Using this method is fine for quick and dirty debug logging, but it can quickly sap performance since it wastes a lot of time allocating memory.

The Solution
To address this issue, I whipped up the following simple class:

class OutputStream{public:   ~OutputStream()   {      Flush();   }public:   OutputStream& Flush()   {      std::wstring outstr = Stream.str();      if(!outstr.empty())      {         OutputMessage(outstr);         Stream.str(L"");      }      return *this;   }   template   OutputStream& operator << (const T& val)   {      Stream << val;      return *this;   }   OutputStream& operator << (std::basic_ostream<wchar_t, std::char_traits<wchar_t> >&     (__cdecl *ptr)(std::basic_ostream<wchar_t, std::char_traits<wchar_t> >&))   {      (*ptr)(Stream);      return Flush();   }protected:   std::wostringstream Stream;};



Examining the Solution in Detail
Let's walk through the code.

The first bit in the destructor ensures that if the object goes out of scope, any remaining output is dumped. The actual Flush() function is responsible for doing that. Included is a check to avoid dumping the output if there isn't anything to say. Flush() automatically clears out the buffer so that you can easily reuse the same object to do multiple output runs.

The next part is where the magic happens. The first operator << does the heavy lifting; any of your standard output usages will flow through here. Note that all this really does is defer the work of creating the buffer to the standard wostringstream class. This also means that any type which can be used with a standard output stream will work with OutputStream as well.

The second operator << is designed to allow you to use std::endl to flush the stream automatically. This means that piping endl into an OutputStream is effectively a call to Flush(), which in turn calls our custom OutputMessage() function.


Using the Code
Putting it into action is easy:

// As soon as stream goes out of scope, "Foo42baz" will be output{   OutputStream stream;   stream << "Foo" << 42 << "baz";}// Or we can do it the more traditional way:OutputStream stream2;stream2 << "Quux" << 343 << "Zimbabwe" << std::endl;


So now you can have all the advantages of easy stream formatting like std::cout would give you, except you can couple it easily to any kind of output function you want, or even multiple output types at the same time.


Further Expansion
In this example, I've used a free function OutputMessage() which presumably actually displays or otherwise handles the messages from the buffer. However, you may find this too simple. A good extension would be to make a class with a pure virtual method OutputMessage(), and turn OutputStream into a template class that can be "attached" to different types of outputters. For example, you could implement a MessageBoxOutputter (which derives from the abstract base class) and then use OutputStream to output messages via a message dialog box.

Another thing to note is that this example does not support the full set of IO formatting functionality provided in the C++ standard library - just endl and other similar IO manipulators. Extending this should be easy: just pass the calls through to the Stream member variable and let it do all the work.

One final note: I have assumed here the use of MBCS/Unicode. If you are still living in the 1990s and refuse to use Unicode, just remove the "w" from wstring and wostringstream, and change wchar_t to char.


Enjoy!
0 likes 1 comments

Comments

furby100
Is the thing "In locus hic, omnes res dementes sunt" yours?

If so, shouldn't it be "In loco hoc"?

My preferred rendition would be "Omnia hic insania fiunt", as a reference to Descartes' quip that "Omnia apud me mathematica fiunt".
March 05, 2008 11:46 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Profile
Author
Advertisement
Advertisement