捕获和引发 std::cout 刷新事件上的事件

Capturing and raising events on std::cout flush events

本文关键字:事件 cout 刷新 std      更新时间:2023-10-16

我正在尝试编写一个作用域对象来重定向std::cout的输出,并在刷新底层缓冲区时调用函数。

我的实现大量借鉴了以下 SO 答案:

C++ 在写入流时执行函数

让它部分工作,但回调函数仅在我显式调用flush时调用 std::cout.但是,我希望它在向流写入任何内容时调用回调函数。

注意:我正在编译 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);
    }
};

现在一个使用示例:

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");
}

我得到以下输出:

sending string to cout...
string sent to cout
flushing cout...
callback(hello!)
cout flushed

同样,我希望在调用std::cout << "hello!";后立即调用该回调函数。 我以为会发生这种情况,因为我正在其构造函数中的ofunctionstream对象上调用setf(std::ios_base::unitbuf)(http://en.cppreference.com/w/cpp/io/manip/unitbuf)。

任何帮助将不胜感激!

如果您检查正在使用的回调的工作原理,它会通过子类化std::streambuf和覆盖overflow()来工作。这一点很重要。

引用C++标准库的相关部分:

27.6.3.2.5 放置区域 [streambuf.pub.put]

int_type痰(char_type c);

返回:如果输出序列写入位置不可用, 返回溢出(特征::to_int_- 类型(c))。否则,将 c 存储在 输出序列的下一个指针,递增指针,以及 返回特征::to_int_type(c)

std::ostream,又名格式化输出,使用 sputc() 写入流缓冲区。因此,唯一一次调用overflow()是在输出缓冲区耗尽时。或明确通过std::flush.

因此,不幸的是,您的选择有些有限。要么处理当前行为,要么将std::streambuf子类绑定为根本没有写入缓冲区,以便最终通过sputc()写入的每个字符都将被戳入overflow(),并调用您的子类实现。

不幸的是,流操作在每次格式化操作后都不会执行任何显式操作,这可能会被std::streambuf截获。它们只是通过 sputc() 写入格式化的输出,一次一个字符,以便输出被收集到 streambuf 的写入缓冲区中,该缓冲区仅在已满或显式使用std::flush时才会被刷新。

所以,这就是它的工作原理。