如何组合输出流,以便输出同时到达多个位置?

发布于 2024-08-12 06:32:23 字数 925 浏览 9 评论 0原文

我想将两个(或更多)流合成为一个。我的目标是任何定向到 coutcerrclog 的输出也与原始流一起输出到文件中。 (例如,当事情记录到控制台时。关闭后,我仍然希望能够返回并查看输出。)

我正在考虑做这样的事情:

class stream_compose : public streambuf, private boost::noncopyable
{
public:
    // take two streams, save them in stream_holder,
    // this set their buffers to `this`.
    stream_compose;

    // implement the streambuf interface, routing to both
    // ...

private:
    // saves the streambuf of an ios class,
    // upon destruction restores it, provides
    // accessor to saved stream
    class stream_holder;

    stream_holder mStreamA;
    stream_holder mStreamB;
};

这看起来很简单。然后 main 中的调用将类似于:

// anything that goes to cout goes to both cout and the file
stream_compose coutToFile(std::cout, theFile);
// and so on

我还查看了 boost::iostreams,但没有看到任何相关内容。

还有其他更好/更简单的方法来实现这一点吗?

I'd like to compose two (or more) streams into one. My goal is that any output directed to cout, cerr, and clog also be outputted into a file, along with the original stream. (For when things are logged to the console, for example. After closing, I'd like to still be able to go back and view the output.)

I was thinking of doing something like this:

class stream_compose : public streambuf, private boost::noncopyable
{
public:
    // take two streams, save them in stream_holder,
    // this set their buffers to `this`.
    stream_compose;

    // implement the streambuf interface, routing to both
    // ...

private:
    // saves the streambuf of an ios class,
    // upon destruction restores it, provides
    // accessor to saved stream
    class stream_holder;

    stream_holder mStreamA;
    stream_holder mStreamB;
};

Which seems straight-forward enough. The call in main then would be something like:

// anything that goes to cout goes to both cout and the file
stream_compose coutToFile(std::cout, theFile);
// and so on

I also looked at boost::iostreams, but didn't see anything related.

Are there any other better/simpler ways to accomplish this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

我爱人 2024-08-19 06:32:23

如果您想纯粹在 stdlib 内完成此操作,那么您确实拥有正确的设计。

一件事:不要在每个输出上使用每个流缓冲区,而是将其实现为使用与给定的流缓冲区之一相同的放置区域,并在溢出和同步时复制到其他流缓冲区。这将最大限度地减少虚拟调用,这是 Streambuf 工作的目标之一。

或者,如果您只想处理 stdout & stderr(这是常见的),通过标准 Unix tee 程序(或您平台上的等效程序)运行您的程序,可以在调用程序时自己执行此操作,也可以在程序中通过分叉、设置适当地向上流,等等。

编辑:你让我思考,我应该知道如何做到这一点。这是我的第一个 近似值。 (当这个中断时,你可以保留这两部分。)

#ifndef INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676
#define INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676

#include <cassert>
#include <cstring>
#include <streambuf>
#include <map>
#include <vector>

template<class CharT, class Traits=std::char_traits<CharT> >
struct basic_streamtee : std::basic_streambuf<CharT, Traits> {
    typedef std::basic_ios<CharT, Traits> Stream;
    typedef std::basic_streambuf<CharT, Traits> StreamBuf;

    typedef typename StreamBuf::char_type char_type;
    typedef typename StreamBuf::traits_type traits_type;
    typedef typename StreamBuf::int_type int_type;
    typedef typename StreamBuf::pos_type pos_type;
    typedef typename StreamBuf::off_type off_type;

    basic_streamtee() : _key_buf(0) {}
    basic_streamtee(Stream& a, Stream& b) : _key_buf(0) {
        this->pubimbue(a.rdbuf()->getloc());
        _set_key_buf(a.rdbuf());
        insert(a);
        insert(b);
    }
    ~basic_streamtee() {
        sync();
        for (typename std::map<Stream*, StreamBuf*>::iterator i = _bufs.begin();
             i != _bufs.end();
             ++i)
        {
            StreamBuf* old = i->first->rdbuf(i->second);
            if (old != this) {
                old->pubsync();
            }
        }
    }

    // add this functionality?
    // streambufs would be unconnected with a stream
    // easy to do by changing _bufs to a multimap
    // and using null pointers for the keys
    //void insert(StreamBuf* buf);
    //void remove(StreamBuf* buf);

    void insert(Stream& s) {
        sync();
        if (!_bufs.count(&s)) {
            if (!_key_buf) {
                _set_key_buf(s.rdbuf());
            }
            _bufs[&s] = s.rdbuf(this);
        }
    }
    void remove(Stream& s) {
        sync();
        typename std::map<Stream*, StreamBuf*>::iterator i = _bufs.find(&s);
        if (i != _bufs.end()) {
            StreamBuf* old = i->second;
            i->first->rdbuf(i->second);
            _bufs.erase(i);

            if (old == _key_buf) {
                _set_key_buf(_bufs.empty() ? 0 : _bufs.begin()->second);
            }
        }
    }

private:
    basic_streamtee(basic_streamtee const&); // not defined
    basic_streamtee& operator=(basic_streamtee const&); // not defined

    StreamBuf* _key_buf;
    std::map<Stream*, StreamBuf*> _bufs;

    void _set_key_buf(StreamBuf* p) {
        //NOTE: does not sync, requires synced already
        _key_buf = p;
        _update_put_area();
    }
    void _update_put_area() {
        //NOTE: does not sync, requires synced already
        if (!_key_buf) {
            this->setp(0, 0);
        }
        else {
            this->setp((_key_buf->*&basic_streamtee::pbase)(),
                       (_key_buf->*&basic_streamtee::epptr)());
        }
    }


#define FOREACH_BUF(var) \
for (typename std::map<Stream*, StreamBuf*>::iterator var = _bufs.begin(); \
var != _bufs.end(); ++var)


    // 27.5.2.4.1 Locales
    virtual void imbue(std::locale const& loc) {
        FOREACH_BUF(iter) {
            iter->second->pubimbue(loc);
        }
    }


    // 27.5.2.4.2 Buffer management and positioning
    //virtual StreamBuf* setbuf(char_type* s, std::streamsize n); // not required
    //virtual pos_type seekoff(off_type off, std::ios_base::seekdir way,
    //                         std::ios_base::openmode which); // not required
    //virtual pos_type seekpos(pos_type sp, std::ios_base::openmode which); // not required
    virtual int sync() {
        if (!_key_buf) {
            return -1;
        }
        char_type* data = this->pbase();
        std::streamsize n = this->pptr() - data;
        (_key_buf->*&basic_streamtee::pbump)(n);
        FOREACH_BUF(iter) {
            StreamBuf* buf = iter->second;
            if (buf != _key_buf) {
                buf->sputn(data, n); //BUG: ignores put errors
                buf->pubsync(); //BUG: ignroes errors
            }
        }
        _key_buf->pubsync(); //BUG: ignores errors
        _update_put_area();
        return 0;
    }


    // 27.5.2.4.3 Get area
    // ignore input completely, teeing doesn't make sense
    //virtual std::streamsize showmanyc();
    //virtual std::streamsize xsgetn(char_type* s, std::streamsize n);
    //virtual int_type underflow();
    //virtual int_type uflow();


    // 27.5.2.4.4 Putback
    // ignore input completely, teeing doesn't make sense
    //virtual int_type pbackfail(int_type c);


    // 27.5.2.4.5 Put area
    virtual std::streamsize xsputn(char_type const* s, std::streamsize n) {
        assert(n >= 0);
        if (!_key_buf) {
            return 0;
        }

        // available room in put area? delay sync if so
        if (this->epptr() - this->pptr() < n) {
            sync();
        }
        // enough room now?
        if (this->epptr() - this->pptr() >= n) {
            std::memcpy(this->pptr(), s, n);
            this->pbump(n);
        }
        else {
            FOREACH_BUF(iter) {
                iter->second->sputn(s, n);
                //BUG: ignores put errors
            }
            _update_put_area();
        }
        return n;
    }
    virtual int_type overflow(int_type c) {
        bool const c_is_eof = traits_type::eq_int_type(c, traits_type::eof());
        int_type const success = c_is_eof ? traits_type::not_eof(c) : c;
        sync();
        if (!c_is_eof) {
            char_type cc = traits_type::to_char_type(c);
            xsputn(&cc, 1);
            //BUG: ignores put errors
        }
        return success;
    }

#undef FOREACH_BUF
};

typedef basic_streamtee<char> streamtee;
typedef basic_streamtee<wchar_t> wstreamtee;

#endif

现在,这个测试还远未完成,但它似乎有效:

#include "streamtee.hpp"

#include <cassert>
#include <iostream>
#include <sstream>

int main() {
    using namespace std;
    {
        ostringstream a, b;
        streamtee tee(a, b);
        a << 42;
        assert(a.str() == "42");
        assert(b.str() == "42");
    }
    {
        ostringstream a, b;
        streamtee tee(cout, a);
        tee.insert(b);
        a << 42 << '\n';
        assert(a.str() == "42\n");
        assert(b.str() == "42\n");
    }
    return 0;
}

将其与文件放在一起:

#include "streamtee.hpp"

#include <iostream>
#include <fstream>

struct FileTee {
  FileTee(std::ostream& stream, char const* filename)
  : file(filename), buf(file, stream)
  {}

  std::ofstream file;
  streamtee buf;
};

int main() {
  using namespace std;

  FileTee out(cout, "stdout.txt");
  FileTee err(clog, "stderr.txt");
  streambuf* old_cerr = cerr.rdbuf(&err.buf);

  cout << "stdout\n";
  clog << "stderr\n";

  cerr.rdbuf(old_cerr);
  // watch exception safety

  return 0;
}

You do have the right design—if you want to do this purely within the stdlib.

One thing: instead of teeing to each streambuf on every output, implement it to use the same put area as one of the streambufs it's given, and copy to the others on overflow and sync. This will minimize virtual calls, which is one of the goals of how streambufs work.

Alternatively, and if you want to only handle stdout & stderr (which is common), run your program through the standard Unix tee program (or the equivalent on your platform), either by doing it yourself when invoking the program, or within the program by forking, setting up the streams as appropriate, etc.

Edit: You got me thinking, and I should know how to get this right. Here's my first approximation. (When this breaks, you get to keep both pieces.)

#ifndef INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676
#define INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676

#include <cassert>
#include <cstring>
#include <streambuf>
#include <map>
#include <vector>

template<class CharT, class Traits=std::char_traits<CharT> >
struct basic_streamtee : std::basic_streambuf<CharT, Traits> {
    typedef std::basic_ios<CharT, Traits> Stream;
    typedef std::basic_streambuf<CharT, Traits> StreamBuf;

    typedef typename StreamBuf::char_type char_type;
    typedef typename StreamBuf::traits_type traits_type;
    typedef typename StreamBuf::int_type int_type;
    typedef typename StreamBuf::pos_type pos_type;
    typedef typename StreamBuf::off_type off_type;

    basic_streamtee() : _key_buf(0) {}
    basic_streamtee(Stream& a, Stream& b) : _key_buf(0) {
        this->pubimbue(a.rdbuf()->getloc());
        _set_key_buf(a.rdbuf());
        insert(a);
        insert(b);
    }
    ~basic_streamtee() {
        sync();
        for (typename std::map<Stream*, StreamBuf*>::iterator i = _bufs.begin();
             i != _bufs.end();
             ++i)
        {
            StreamBuf* old = i->first->rdbuf(i->second);
            if (old != this) {
                old->pubsync();
            }
        }
    }

    // add this functionality?
    // streambufs would be unconnected with a stream
    // easy to do by changing _bufs to a multimap
    // and using null pointers for the keys
    //void insert(StreamBuf* buf);
    //void remove(StreamBuf* buf);

    void insert(Stream& s) {
        sync();
        if (!_bufs.count(&s)) {
            if (!_key_buf) {
                _set_key_buf(s.rdbuf());
            }
            _bufs[&s] = s.rdbuf(this);
        }
    }
    void remove(Stream& s) {
        sync();
        typename std::map<Stream*, StreamBuf*>::iterator i = _bufs.find(&s);
        if (i != _bufs.end()) {
            StreamBuf* old = i->second;
            i->first->rdbuf(i->second);
            _bufs.erase(i);

            if (old == _key_buf) {
                _set_key_buf(_bufs.empty() ? 0 : _bufs.begin()->second);
            }
        }
    }

private:
    basic_streamtee(basic_streamtee const&); // not defined
    basic_streamtee& operator=(basic_streamtee const&); // not defined

    StreamBuf* _key_buf;
    std::map<Stream*, StreamBuf*> _bufs;

    void _set_key_buf(StreamBuf* p) {
        //NOTE: does not sync, requires synced already
        _key_buf = p;
        _update_put_area();
    }
    void _update_put_area() {
        //NOTE: does not sync, requires synced already
        if (!_key_buf) {
            this->setp(0, 0);
        }
        else {
            this->setp((_key_buf->*&basic_streamtee::pbase)(),
                       (_key_buf->*&basic_streamtee::epptr)());
        }
    }


#define FOREACH_BUF(var) \
for (typename std::map<Stream*, StreamBuf*>::iterator var = _bufs.begin(); \
var != _bufs.end(); ++var)


    // 27.5.2.4.1 Locales
    virtual void imbue(std::locale const& loc) {
        FOREACH_BUF(iter) {
            iter->second->pubimbue(loc);
        }
    }


    // 27.5.2.4.2 Buffer management and positioning
    //virtual StreamBuf* setbuf(char_type* s, std::streamsize n); // not required
    //virtual pos_type seekoff(off_type off, std::ios_base::seekdir way,
    //                         std::ios_base::openmode which); // not required
    //virtual pos_type seekpos(pos_type sp, std::ios_base::openmode which); // not required
    virtual int sync() {
        if (!_key_buf) {
            return -1;
        }
        char_type* data = this->pbase();
        std::streamsize n = this->pptr() - data;
        (_key_buf->*&basic_streamtee::pbump)(n);
        FOREACH_BUF(iter) {
            StreamBuf* buf = iter->second;
            if (buf != _key_buf) {
                buf->sputn(data, n); //BUG: ignores put errors
                buf->pubsync(); //BUG: ignroes errors
            }
        }
        _key_buf->pubsync(); //BUG: ignores errors
        _update_put_area();
        return 0;
    }


    // 27.5.2.4.3 Get area
    // ignore input completely, teeing doesn't make sense
    //virtual std::streamsize showmanyc();
    //virtual std::streamsize xsgetn(char_type* s, std::streamsize n);
    //virtual int_type underflow();
    //virtual int_type uflow();


    // 27.5.2.4.4 Putback
    // ignore input completely, teeing doesn't make sense
    //virtual int_type pbackfail(int_type c);


    // 27.5.2.4.5 Put area
    virtual std::streamsize xsputn(char_type const* s, std::streamsize n) {
        assert(n >= 0);
        if (!_key_buf) {
            return 0;
        }

        // available room in put area? delay sync if so
        if (this->epptr() - this->pptr() < n) {
            sync();
        }
        // enough room now?
        if (this->epptr() - this->pptr() >= n) {
            std::memcpy(this->pptr(), s, n);
            this->pbump(n);
        }
        else {
            FOREACH_BUF(iter) {
                iter->second->sputn(s, n);
                //BUG: ignores put errors
            }
            _update_put_area();
        }
        return n;
    }
    virtual int_type overflow(int_type c) {
        bool const c_is_eof = traits_type::eq_int_type(c, traits_type::eof());
        int_type const success = c_is_eof ? traits_type::not_eof(c) : c;
        sync();
        if (!c_is_eof) {
            char_type cc = traits_type::to_char_type(c);
            xsputn(&cc, 1);
            //BUG: ignores put errors
        }
        return success;
    }

#undef FOREACH_BUF
};

typedef basic_streamtee<char> streamtee;
typedef basic_streamtee<wchar_t> wstreamtee;

#endif

Now, this test is far from complete, but it seems to work:

#include "streamtee.hpp"

#include <cassert>
#include <iostream>
#include <sstream>

int main() {
    using namespace std;
    {
        ostringstream a, b;
        streamtee tee(a, b);
        a << 42;
        assert(a.str() == "42");
        assert(b.str() == "42");
    }
    {
        ostringstream a, b;
        streamtee tee(cout, a);
        tee.insert(b);
        a << 42 << '\n';
        assert(a.str() == "42\n");
        assert(b.str() == "42\n");
    }
    return 0;
}

Put it together with a file:

#include "streamtee.hpp"

#include <iostream>
#include <fstream>

struct FileTee {
  FileTee(std::ostream& stream, char const* filename)
  : file(filename), buf(file, stream)
  {}

  std::ofstream file;
  streamtee buf;
};

int main() {
  using namespace std;

  FileTee out(cout, "stdout.txt");
  FileTee err(clog, "stderr.txt");
  streambuf* old_cerr = cerr.rdbuf(&err.buf);

  cout << "stdout\n";
  clog << "stderr\n";

  cerr.rdbuf(old_cerr);
  // watch exception safety

  return 0;
}
温柔嚣张 2024-08-19 06:32:23

您提到在 Boost.IOStreams 中没有找到任何内容。您是否考虑过 tee_device

You mention having not found anything in Boost.IOStreams. Did you consider tee_device?

永言不败 2024-08-19 06:32:23

我会编写一个自定义流缓冲区,它仅将数据转发到所有链接流的缓冲区。

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <functional>

class ComposeStream: public std::ostream
{
    struct ComposeBuffer: public std::streambuf
    {
        void addBuffer(std::streambuf* buf)
        {
            bufs.push_back(buf);
        }
        virtual int overflow(int c)
        {
            // std::for_each(bufs.begin(),bufs.end(),std::bind2nd(std::mem_fun(&std::streambuf::sputc),c));

            // In C++20 we can simplify this:
            // Thanks: @nabelekt
            for (auto& buf: bufs) {
                buf->sputc(c);
            }
  
            return c;
        }

        private:
            std::vector<std::streambuf*>    bufs;
            
    };  
    ComposeBuffer myBuffer;
    public: 
        ComposeStream()
            :std::ostream(NULL)
        {
            std::ostream::rdbuf(&myBuffer);
        }   
        void linkStream(std::ostream& out)
        {
            out.flush();
            myBuffer.addBuffer(out.rdbuf());
        }
};
int main()
{
    ComposeStream   out;
    out.linkStream(std::cout);
    out << "To std::cout\n";

    out.linkStream(std::clog);
    out << "To: std::cout and std::clog\n";

    std::ofstream   file("Plop");
    out.linkStream(file);
    out << "To all three locations\n";
}

I would write a custom stream buffer that just forwards data to the buffers of all your linked streams.

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <functional>

class ComposeStream: public std::ostream
{
    struct ComposeBuffer: public std::streambuf
    {
        void addBuffer(std::streambuf* buf)
        {
            bufs.push_back(buf);
        }
        virtual int overflow(int c)
        {
            // std::for_each(bufs.begin(),bufs.end(),std::bind2nd(std::mem_fun(&std::streambuf::sputc),c));

            // In C++20 we can simplify this:
            // Thanks: @nabelekt
            for (auto& buf: bufs) {
                buf->sputc(c);
            }
  
            return c;
        }

        private:
            std::vector<std::streambuf*>    bufs;
            
    };  
    ComposeBuffer myBuffer;
    public: 
        ComposeStream()
            :std::ostream(NULL)
        {
            std::ostream::rdbuf(&myBuffer);
        }   
        void linkStream(std::ostream& out)
        {
            out.flush();
            myBuffer.addBuffer(out.rdbuf());
        }
};
int main()
{
    ComposeStream   out;
    out.linkStream(std::cout);
    out << "To std::cout\n";

    out.linkStream(std::clog);
    out << "To: std::cout and std::clog\n";

    std::ofstream   file("Plop");
    out.linkStream(file);
    out << "To all three locations\n";
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文