流的重定向

原地址:http://devmaster.net/forums/topic/5721-ostream-is-easy/

不得不说,外国的网站内容好东西多点。反观国内网站,找这样的东西是不容易的,与流相关的全是教科书上面最基本的用法,什么用流读取文件啦,什么输出的屏幕啦,你们发这些东西不觉得脸红吗?

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).

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:

const int BufferSize = 256;


class DebugBuf : public BufferedStringBuf
{
public:
DebugBuf() : BufferedStringBuf(BufferSize) {}
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;
std::cout << "to visual studio debug output window\x0001\x0010xyz" << std::endl;
// restore the old buffer
std::cout.rdbuf(old_buffer);
std::cout << "xyz" << endl;


Here is another example buffer:

class MessageBoxBuf : public BufferedStringBuf
{
public:
MessageBoxBuf() : BufferedStringBuf(BufferSize) {}
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

posted @ 2012-02-18 15:24  littlestone08  阅读(312)  评论(0)    收藏  举报