Override c++ streams
In a C++ program, we have 3 streams: stdin
, stdout
, and stderr
. Can I override these in a console application and use them in a application that uses forms?
For example, if in som开发者_开发百科e base class, I have cout<< "..."
, can I "redirect" to something visual (like a Windows Form)?
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);
The usual way to put an iostream
interface on something else is to use the stringstream
/istringstream
/ostringstream
classes in <sstream>
.
The best way is to change the use of cin
, cout
, etc to parameters of type istream &
and ostream &
. If that is really impossible, you can modify cin.rdbuf()
to point at the stream you wish to modify, but that is extremely hackish and won't work with multithreading.
Copy text from the form into an istringstream
, then pass it to the existing code as an istream &
. When you're done, read the results from the stringstream
passed as ostream &
that replaced cout
and copy it into the form.
Do not declare arguments of type istringstream &
or ostringstream &
. Use the base classes.
If you really want, you could subclass std::basic_stringbuf< char >
and override the sync
and underflow
virtual functions to tie streams directly to text fields. But that's fairly advanced and likely not worth it.
精彩评论