Подтвердить что ты не робот

Как я могу составить выходные потоки, поэтому вывод идет сразу несколькими местами?

Я хотел бы создать два (или более) потока в один. Моя цель заключается в том, что любой вывод, направленный на 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, но не видел ничего связанного.

Есть ли другие лучшие/более простые способы для этого?

4b9b3361

Ответ 1

Вы упомянули, что ничего не нашли в Boost.IOStreams. Вы считали tee_device?

Ответ 2

У вас есть правильный дизайн и если вы хотите сделать это исключительно в stdlib.

Одна вещь: вместо teeing для каждого streambuf на каждом выходе реализуйте его, чтобы использовать ту же область ввода, что и один из предоставленных streambufs, и копировать их другим при переполнении и синхронизации. Это минимизирует виртуальные вызовы, что является одной из целей работы streambufs.

В качестве альтернативы, и если вы хотите обрабатывать только 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;
}

Ответ 3

Я бы написал специальный буфер потока, который просто пересылает данные в буферы всех связанных потоков.

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