Capturing and raising events on std::cout flush events

822 views Asked by At

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!

1

There are 1 answers

0
Sam Varshavchik On BEST ANSWER

If you examine how the callback that you're using works, it works by subclassing std::streambuf and by overriding overflow(). That's important to note.

Quoting the relevant parts of the C++ standard library:

27.6.3.2.5 Put area [streambuf.pub.put]

int_type sputc(char_type c);

Returns: If the output sequence write position is not available, returns overflow(traits::to_int_- type(c)). Otherwise, stores c at the next pointer for the output sequence, increments the pointer, and returns traits::to_int_type(c)

std::ostream, a.k.a. formatted output, writes to the stream buffer using sputc(). So, the only time overflow() gets invoked is when the output buffer is exhausted. Or explicitly via std::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 via sputc() is going to get punted to overflow(), 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, via sputc(), so that the output gets collected in the streambuf's write buffer, which gets flushed out only when it's full, or when std::flush gets explicitly used.

So, that's how it works.