Question

Right, I'm not even sure how to correctly formulate this; I feel it's a bit of an involved question. I'm sure someone can help me out here though.

This is what I want to do:

  1. Have a single class that I can send stuff to, like this.

    icl << "Blah blah blah" << std::endl;
    
  2. I want to be able to .attach() classes that inherit std::basic_ostream to it.

  3. Those classes would then be able to format the output their own way. One might add a timestamp and write to a log, the other might write it to the console, the other might display it in-game.

Anyone care to get me started in the right direction? Here's the idea I pretty much have.

#include <vector>

class OutputMan {
    std::vector<std::basic_ostream&> m_Streams;

public:
    void attach(std::basic_ostream& os) {
        m_Streams.push_back(os);
    }
}

Question #1: What do I need to inherit and override to send

icl << "Blah!" << std::endl;

To every stream in m_Streams?

Question #2: How do I inherit std::basic_ostream and create a class that changes the output to, for example, add a timestamp to the start of it? I also want for this class to output to a file.

Was it helpful?

Solution

I think I'd do things a bit differently. I've probably made this just a bit more elaborate than necessary -- I'm afraid I may have gotten a little carried away with trying to put new C++11 features to good use. Anyway, on with the code:

#include <streambuf>
#include <fstream>
#include <vector>
#include <iostream>
#include <initializer_list>

namespace multi { 
class buf: public std::streambuf {
    std::vector<std::streambuf *> buffers;
public:
    typedef std::char_traits<char> traits_type;
    typedef traits_type::int_type  int_type;

    buf(std::vector<std::ofstream> &buf) {
        for (std::ofstream &os : buf)
            buffers.push_back(os.rdbuf());
    }

    void attach(std::streambuf *b) { buffers.push_back(b); }

    int_type overflow(int_type c) {
        bool eof = false;
        for (std::streambuf *buf : buffers) 
            eof |= (buf -> sputc(c) == traits_type::eof());
        return eof ? traits_type::eof() : c;
    }   
};

class stream : public std::ostream { 
    std::vector<std::ofstream> streams;
    buf outputs;
public:   
    stream(std::initializer_list<std::string> names)
        : streams(names.begin(), names.end()), 
          outputs(streams), 
          std::ostream(&outputs) 
    { }
    void attach(std::ostream &b) {
        outputs.attach(b.rdbuf());
    }
};
}

int main() { 
    multi::stream icl({"c:\\out1.txt", "c:\\out2.txt"});
    icl.attach(std::cout);

    icl << "Blah blah blah" << std::endl;
}

As you can see, this already accepts manipulators (which should work with any manipulator, not just std::endl). If you want to write to multiple files (things that could/can be opened as fstreams) you can specify as many of those names in the constructor as you like (within the limits imposed by your system, of course). For things like std::cout and std::cerr for which you don't necessarily have a file name, you can use attach as you originally intended.

I suppose I should add that I'm not entirely happy with this as-is. It'd take some fairly serious rewriting to do it, but after some thought, I think the "right" way would probably be for multi::stream's ctor to be a variadic template instead, so you'd be able to do something like: multi::stream icl("c:\\out1.txt", std::cout);, and it would sort out how to use each parameter based on its type. I may update this answer to include that capability some time soon.

As far as the second question goes, I've written another answer that covers the basic idea, but is probably a bit overly elaborate, so the part you care about may kind of get lost in the shuffle, so to speak -- it has quite a bit of logic to deal with line lengths that you don't really care about (but does produce each output line with a specified prefix, like you want).

OTHER TIPS

You may need something like this:

class OutputMan {
    std::vector<std::ostream*> m_Streams;

public:
    void attach(std::ostream *os) {
        m_Streams.push_back(os);
    }

    template <typename T>
    OutputMan &operator<<(const T &t) {

        for (int i=0; i<m_Streams.size(); i++)
            *m_Streams[i] << t;

        return *this;
    }
};

int main() {
    ofstream file("test.txt");

    OutputMan x;
    x.attach(&cout);
    x.attach(&cerr);
    x.attach(&file);

    x << "Hello" << 123;
}

For simplicity I used std::ostream*. To accept stuffs by << I overloaded operator<<.

 

Note: If you want OutputMan accepts std::endl as well as other things, read here.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top