2009-11-19 22 views
18

Tôi muốn soạn hai (hoặc nhiều) luồng thành một. Mục tiêu của tôi là bất kỳ đầu ra nào hướng đến cout, cerrclog cũng được xuất thành một tệp, cùng với luồng gốc. (. Đối với khi mọi thứ đang đăng nhập vào giao diện điều khiển, ví dụ Sau khi kết thúc, tôi muốn vẫn có thể quay trở lại và xem kết quả.)Tôi có thể soạn luồng đầu ra như thế nào, vì vậy đầu ra có thể xuất hiện nhiều địa điểm cùng một lúc?

Tôi đã nghĩ đến việc làm một cái gì đó như thế này:

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

Điều này dường như đủ về phía trước. Cuộc gọi trong chính sau đó sẽ là một cái gì đó như:

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

Tôi cũng xem boost::iostreams, nhưng không thấy bất cứ điều gì liên quan.

Có cách nào khác tốt hơn/đơn giản hơn để thực hiện việc này không?

+0

Tôi thích câu trả lời ở đây: http://stackoverflow.com/a/13978705/2662901 – feetwet

Trả lời

11

Bạn đề cập đến không tìm thấy gì trong Boost.IOStreams. Bạn có xem xét tee_device không?

12

Bạn có thiết kế phù hợp — nếu bạn muốn thực hiện điều này hoàn toàn trong stdlib.

Một điều: thay vì phát cho mỗi streambuf trên mọi đầu ra, hãy thực hiện nó để sử dụng cùng một vùng đặt làm một trong các streambuf được đưa ra và sao chép sang những người khác đang tràn và đồng bộ. Điều này sẽ giảm thiểu các cuộc gọi ảo, đó là một trong những mục tiêu của cách hoạt động của streambuf.

Ngoài ra, và nếu bạn muốn chỉ xử lý stdout & stderr (phổ biến), chạy chương trình của bạn thông qua chương trình Unix tee tiêu chuẩn (hoặc tương đương trên nền tảng của bạn), hoặc bằng cách tự thực hiện khi gọi chương trình, hoặc trong chương trình bằng cách giả mạo, thiết lập các luồng thích hợp, v.v.

Chỉnh sửa: Bạn đã suy nghĩ và tôi nên biết cách làm đúng. Đây là số firstapproximation của tôi. (Khi điều này phá vỡ, bạn có thể giữ cả hai miếng.)

#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 

Bây giờ, thử nghiệm này là xa hoàn thành, nhưng nó dường như làm việc:

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

Đặt nó cùng với một tập tin:

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

Vì vậy, tôi cũng đã được triển khai, mã của chúng tôi khá gần, mặc dù tôi chia tôi thành hai lớp (một streambufs của tee, các luồng tee'd khác, sử dụng tee cũ). Tuy nhiên, Eric đã chỉ ra rằng tăng cường thực hiện điều này đã có, vì vậy nếu tôi có thể nhận được làm việc của mình như thế nào tôi hy vọng, tôi sẽ chấp nhận câu trả lời của mình. Nhưng bạn nên biết bài đăng này rất hữu ích, tôi học được rất nhiều từ nó. :) – GManNickG

+0

Giải pháp tuyệt vời! Liên kết không bị hỏng: https://bitbucket.org/kniht/scraps/src/01ecc1346bc5/cpp/kniht/streamtee.hpp –

11

Tôi sẽ viết bộ đệm luồng tùy chỉnh chỉ chuyển tiếp dữ liệu đến bộ đệm của tất cả các luồng được liên kết của bạn.

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

Bạn có một vài lỗi trong đó, nhân tiện. Điều này tương tự như những gì tôi đã có cho đến nay, thực sự.Vấn đề là tôi sẽ cần phải làm ba, bởi vì tôi không thể có 'cout' và' cerr' liên kết với nhau. (Chúng xuất ra cùng một nơi, theo mặc định) – GManNickG

+0

biên dịch và chạy cho tôi. Nếu clog và cout đang đi đến cùng một nơi không phải là vấn đề của ComposeStream. –

+0

Đẹp và đơn giản. Tuy nhiên, nó sẽ không cho phép mã đã sử dụng cout/clog/etc. để có được đầu ra của họ vào luồng sáng tác. (Điều này chắc chắn có thể là một lợi ích, nhưng nó không phải là những gì đã được yêu cầu, cách tôi đọc nó.) –

Các vấn đề liên quan