Como posso compor streams de saída, então a saída passa por vários lugares ao mesmo tempo?

Eu gostaria de compor dois (ou mais) streams em um. Meu objective é que qualquer saída direcionada para cout , cerr e clog também seja enviada para um arquivo, junto com o stream original. (Para quando as coisas estão conectadas ao console, por exemplo. Após o fechamento, eu ainda gostaria de poder voltar e ver a saída.)

Eu estava pensando em fazer algo assim:

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

Que parece simples o suficiente. A chamada no main seria algo como:

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

Eu também olhei para boost::iostreams , mas não vi nada relacionado.

Existem outras maneiras melhores / mais simples de realizar isso?

    Você mencionou não ter encontrado nada no Boost.IOStreams. Você considerou tee_device ?

    Você tem o design certo – se quiser fazer isso apenas dentro do stdlib.

    Uma coisa: em vez de teeing para cada streambuf em cada saída, implemente-o para usar a mesma área de colocação de um dos streambufs que ele forneceu, e copie para os outros em overflow e sync. Isso minimizará as chamadas virtuais, que é um dos objectives de como os streambufs funcionam.

    Alternativamente, e se você quiser apenas lidar com stdout & stderr (que é comum), execute seu programa através do programa padrão Unix tee (ou o equivalente em sua plataforma), seja fazendo você mesmo ao invocar o programa, ou dentro do programa bifurcando, configurando os streams conforme apropriado, etc.

    Edit: Você me pegou pensando, e eu deveria saber como fazer isso direito. Aqui está minha primeira aproximação . (Quando isso quebra, você consegue manter os dois pedaços.)

     #ifndef INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676 #define INCLUDE_GUARD_A629F54A136C49C9938CB33EF8EDE676 #include  #include  #include  #include  #include  template > struct basic_streamtee : std::basic_streambuf { typedef std::basic_ios Stream; typedef std::basic_streambuf 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::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::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 _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::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 streamtee; typedef basic_streamtee wstreamtee; #endif 

    Agora, este teste está longe de terminar, mas parece funcionar:

     #include "streamtee.hpp" #include  #include  #include  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; } 

    Coloque junto com um arquivo:

     #include "streamtee.hpp" #include  #include  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; } 

    Gostaria de escrever um buffer de stream personalizado que apenas encaminha os dados para os buffers de todos os seus streams vinculados.

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