代码之家  ›  专栏  ›  技术社区  ›  GManNickG

如何组合输出流,以便一次输出多个位置?

  •  18
  • GManNickG  · 技术社区  · 15 年前

    我想把两个(或多个)流合成一个流。我的目标是任何输出指向 cout , cerr clog 还可以与原始流一起输出到文件中。(例如,当事件记录到控制台时。关闭后,我仍希望能够返回并查看输出。)

    我想做这样的事:

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

    这似乎足够直接。然后,主调用如下所示:

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

    我也看过 boost::iostreams 但没有看到任何相关的信息。

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

    3 回复  |  直到 13 年前
        1
  •  11
  •   Benjamin Lindley    13 年前

    你提到在boost.iostreams中没有发现任何东西。你考虑过吗 tee_device ?

        2
  •  13
  •   Roger Pate    15 年前

    如果您只想在stdlib中进行设计,那么您的设计是正确的。

    一件事:不要在每个输出上设置每个streambuf,而是实现它使用与给定的其中一个streambuf相同的放置区域,并在溢出和同步时复制到其他区域。这将最小化虚拟调用,这是streambufs如何工作的目标之一。

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

    编辑: 你让我想起来了,我应该知道怎么做才对。这是我的 first approximation . (当这段时间结束时,你可以保留这两段。)

    #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;
    }
    
        3
  •  11
  •   Loki Astari    15 年前

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

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