views:

318

answers:

3

I'd like to compose two (or more) streams into one. My goal is that any output directed to cout, cerr, and clog also be outputted into a file, along with the original stream. (For when things are logged to the console, for example. After closing, I'd like to still be able to go back and view the output.)

I was thinking of doing something like this:

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

Which seems straight-forward enough. The call in main then would be something like:

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

I also looked at boost::iostreams, but didn't see anything related.

Are there any other better/simpler ways to accomplish this?

+6  A: 
Roger Pate
So I got mine implemented as well, our code was fairly close, though I split mine into two classes (one tee's streambufs, the other tee'd streams, which used the former tee). However, Eric has pointed out that boost implements this already, so if I can get his working how I'm hoping, I'll accept his answer. But you should know this post was very helpful, I learn a lot from it. :)
GMan
+3  A: 

I would write a custom stream buffer that just forwards data to the buffers of all your linked streams.

#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";
}
Martin York
You have a few errors in there, by the way. This is similar to what I had so far, actually. The problem is I'll need to make three, because I can't have `cout` and `cerr` linked to each other. (They output to the same place, by default)
GMan
compiles and runs for me. If clog and cout are going to the same place is not the problem of the ComposeStream.
Martin York
Nice and simple. However, it won't allow code already using cout/clog/etc. to get their output into the composed stream. (This may certainly be a benefit, but it's not what was asked for, the way I read it.)
Roger Pate
cout and cerr are not linked to each other, unless your implementation does that. One writes to stdout and the other to stderr. clog and cerr both write to stderr though, is that what you meant?
Roger Pate
GMan
Martin York
Oops, didn't notice the first one. You'll have to pass it 0, since the standard doesn't give it a default value.
GMan
@GMan: done. :-)
Martin York
Though I didn't end up using your or Pate's designs, this answer was still helpful, so thank you. :)
GMan
+4  A: 

You mention having not found anything in Boost.IOStreams. Did you consider tee_device?

Éric Malenfant
Hm, I never heard the term "tee" until Pate's answer, and it never struck me to research boost with my newly learned term. I'm working with it now to make sure I can get it working with my desired goal.
GMan
I got everything working perfectly. Make a couple generic helper classes that wrap a few lines into a one, and it works great. :) Thanks for finding it, saved a lot of work. (The final call is just: `utility::stream_split coutToFile(std::cout, outputLog);`, which itself doesn't do much beside utilize `boost::tee_device`)
GMan