I'm trying to write a scoped object to redirect output from std::cout
and call a function when it's underlying buffer is flushed.
My implementation borrows heavily from the following SO answer:
c++ execute function any time a stream is written to
I have it partially working, but the callback function is only called when I explicity call flush
on std::cout
. However, I want it to call the callback function whenever anything is written to the stream.
NOTE: I am compiling aginst MSVC++11.
struct stream_redirect
{
stream_redirect(std::ostream& stream, std::streambuf* rdbuf) :
stream(stream),
rdbuf_old(stream.rdbuf())
{
stream.set_rdbuf(rdbuf);
}
~stream_redirect()
{
stream.set_rdbuf(rdbuf_old);
}
private:
stream_redirect(const stream_redirect&) = delete;
stream_redirect& operator = (const stream_redirect&) = delete;
std::ostream& stream;
std::streambuf* rdbuf_old;
};
struct functionbuf : public std::streambuf
{
typedef std::function<void(std::string)> function_type;
functionbuf(const function_type& function)
: function(function)
{
setp(buffer, buffer + sizeof(buffer) - 1);
}
private:
char buffer[1024];
function_type function;
virtual int_type overflow(int_type c) override
{
if (!traits_type::eq_int_type(c, traits_type::eof()))
{
*this->pptr() = traits_type::to_char_type(c);
pbump(1);
}
return sync() ? traits_type::not_eof(c) : traits_type::eof();
}
virtual int_type sync() override
{
if (pbase() != pptr())
{
function(std::string(pbase(), pptr()));
setp(pbase(), epptr());
}
return 0;
}
};
struct ofunctionstream :
private virtual functionbuf,
public std::ostream
{
ofunctionstream(const function_type& function) :
functionbuf(function),
std::ostream(static_cast<std::streambuf*>(this))
{
setf(std::ios_base::unitbuf);
}
};
Now a usage example:
void callback(std::string string)
{
printf("callback(%s)\n", string.c_str());
}
int main()
{
ofunctionstream fs(&callback);
stream_redirect sr(std::cout, fs.rdbuf());
printf("sending string to cout...");
std::cout << "hello!";
printf("string sent to cout");
//this is necessary to
printf("flushing cout...");
std::cout.flush();
printf("cout flushed");
}
I get the following output:
sending string to cout...
string sent to cout
flushing cout...
callback(hello!)
cout flushed
Again, I want that callback function to be called as soon as std::cout << "hello!";
is called. I assumed this would happen since I am calling setf(std::ios_base::unitbuf)
(http://en.cppreference.com/w/cpp/io/manip/unitbuf) on the ofunctionstream
object in it's constructor.
Any help is greatly appreciated!
If you examine how the callback that you're using works, it works by subclassing
std::streambuf
and by overridingoverflow()
. That's important to note.Quoting the relevant parts of the C++ standard library:
std::ostream
, a.k.a. formatted output, writes to the stream buffer using sputc(). So, the only timeoverflow()
gets invoked is when the output buffer is exhausted. Or explicitly viastd::flush
.So, your options are, unfortunately, somewhat limited. Either deal with the current behavior, or jury-rig the
std::streambuf
subclass to have no write buffer at all, so that every character that ends up getting written viasputc()
is going to get punted tooverflow()
, and invoke your subclass implementation.Unfortunately, stream operations do not carry out any explicit action after every formatting operation, that can be intercepted by the
std::streambuf
. They just write the formatted output, one character at a time, viasputc()
, so that the output gets collected in the streambuf's write buffer, which gets flushed out only when it's full, or whenstd::flush
gets explicitly used.So, that's how it works.