Override c++ streams
What I would recommend doing is having a class which wraps around an iostream like this :
#include <iostream>
#define LOG Log()
class Log
{
public:
Log(){}
~Log()
{
// Add an newline.
std::cout << std::endl;
}
template<typename T>
Log &operator << (const T &t)
{
std::cout << t;
return * this;
}
};
Then, whenever you want to change where the data goes, you just change the class behavior. Here is how you use the class:
LOG << "Use this like an iostream.";
[edit] As potato swatter suggested, I'll add an example with something other than cout:
#include <sstream>
#define LOG Log()
// An example with a string stream.
class Log
{
private:
static std::stringstream buf;
public:
Log(){}
~Log()
{
// Add an newline.
buf << std::endl;
}
template<typename T>
Log &operator << (const T &t)
{
buf << t;
return * this;
}
};
// Define the static member, somewhere in an implementation file.
std::stringstream Log::buf;
As for why you should try this instead of inheriting from something like a string stream, mainly because you can easily change where the Logger outputs to dynamically. For instance, you could have three different output streams, and use a static member variable to swap between at runtime:
class Log
{
private:
static int outputIndex = 0;
// Add a few static streams in here.
static std::stringstream bufOne;
static std::stringstream bufTwo;
static std::stringstream bufThree;
public:
// Constructor/ destructor goes here.
template<typename T>
Log &operator << (const T &t)
{
// Switch between different outputs.
switch (outputIndex)
{
case 1:
bufOne << t;
break;
case 2:
bufTwo << t;
case 3:
bufThree << t;
default:
std::cout << t;
break;
}
return * this;
}
static void setOutputIndex(int _outputIndex)
{
outputIndex = _outputIndex;
}
};
// In use
LOG << "Print to stream 1";
Log::setOutputIndex(2);
LOG << "Print to stream 2";
Log::setOutputIndex(3);
LOG << "Print to stream 3";
Log::setOutputIndex(0);
LOG << "Print to cout";
This can easily be expanded to create a powerful way of dealing with logging. You could add filestreams, use std::cerr, etc.
Here's the code I use to redirect std::cout
to a GUI on Windows:
struct STDOUT_BLOCK : SLIST_ENTRY
{
char sz[];
};
class capturebuf : public std::stringbuf
{
protected:
virtual int sync()
{
if (g_threadUI && g_hwndProgressDialog) {
// ensure NUL termination
overflow(0);
// allocate space
STDOUT_BLOCK* pBlock = (STDOUT_BLOCK*)_aligned_malloc(sizeof *pBlock + pptr() - pbase(), MEMORY_ALLOCATION_ALIGNMENT);
// copy buffer into string
strcpy(pBlock->sz, pbase());
// clear buffer
str(std::string());
// queue string
::InterlockedPushEntrySList(g_slistStdout, pBlock);
// kick to log window
::PostMessageA(g_hwndProgressDialog, WM_APP, 0, 0);
}
return __super::sync();
}
};
Then inside main()
:
capturebuf altout;
std::cout.set_rdbuf(&altout);
Of course, you then need to handle the WM_APP
message in your window procedure and pull the strings off the SList. But this handles the cout
redirection part.
As jweyrich correctly notes, you need to change the streambuf*
back before altout
goes out of scope. This code will do so:
struct scoped_cout_streambuf_association
{
std::streambuf* orig;
scoped_cout_streambuf_association( std::streambuf& buf )
: orig(std::cout.rdbuf())
{
std::cout.rdbuf(&buf);
}
~scoped_cout_streambuf_association()
{
std::cout.rdbuf(orig);
}
};
And inside main
:
capturebuf altout;
scoped_cout_streambuf_association redirect(altout);