我怎样才能撰写输出stream,所以输出一次会有多个地方?

我想编写两个(或更多)stream成一个。 我的目标是任何针对coutcerrclog输出都会与原始stream一起输出到文件中。 (例如,当事情被logging到控制台,例如closures后,我想仍然能够返回并查看输出。)

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

 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 ,但没有看到任何相关的东西。

还有其他更好/更简单的方法来完成吗?

你提到在Boost.IOStreams中没有find任何东西。 你有没有考虑过tee_device ?

你有正确的devise – 如果你想纯粹在stdlib中做这个。

有一件事情:不要在每个输出上对每个stream缓冲区进行开发,而是将其实现为使用与其给定的一个stream缓冲区相同的放置区域,并在溢出和同步时复制到其他stream缓冲区。 这将最大限度地减less虚拟呼叫,这是streambuf工作的目标之一。

或者,如果只想处理stdout&stderr(这很常见),可以通过标准的Unix tee程序(或平台上的等价物)运行程序,或者在调用程序时自己执行,或者在程序中通过分叉,适当设置stream等。

编辑:你让我思考,我应该知道如何得到这个权利。 这是我的第一个 近似值 。 (当这个打破的时候,你会保持这两件)。

 #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 

现在,这个testing还远远没有完成,但似乎工作:

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

我会写一个自定义stream缓冲区,只是将数据转发到所有链接stream的缓冲区。

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