Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to write vector of ostreams in C++ which takes in all the different output streams like cout, ostringstream and ofstream

I am trying to implement a logger which can be registered with multiple streams like ostringstream, ofstream etc. I tried to implement the function like this

void register_stream(std::ostream& a);

The vector is as follows

std::vector<std::ostream> streams;

The register stream and operator overloading is as follows

void logger::register_stream(std::ostream &a)`

{

    streams.push_back(a);

}

template <typename T>

void logger::operator<<(T const& value)

{

    for (auto stream : streams)

    {

        (stream) << value;

    }

}

I am trying to implement a logger to write to all registered streams on a single operator "<<" call.

The following is the invocation code:

std::ostringstream os;
    std::ofstream f;
    logger l;
    l.register_stream(f);
    l.register_stream(os);
    l << "log this";

I am getting an error:

C2280: std::basic_ostream<char,std::char_traits<char>>::basic_ostream(const std::basic_ostream<char,std::char_traits<char>> &): attempting to reference a deleted function

Any help would be much appreciated.

like image 714
imtj1990 Avatar asked Mar 05 '23 11:03

imtj1990


2 Answers

ostream does both formatting and writing into the underlying streambuf. So that when you use operator<< multiple times it formats the same input multiple times unnecessarily. A more optimal approach is to format once and then copy the formatted output to the multiple underlying streams using unformatted output function ostream::write.

It is convenient to have std::ostream interface so that you can pass it to existing functions expecting std::ostream interface.

You basically need a custom streambuf implementation. Writing one from scratch is good learning experience but tedious and error prone because streambuf interface is somewhat hard to comprehend and implement correctly. Use The Boost Iostreams Library instead.

Working example:

#include <boost/iostreams/stream.hpp>
#include <algorithm>
#include <iostream>
#include <vector>

struct MultiSink {
    using char_type = char;

    struct category
        : boost::iostreams::sink_tag
        , boost::iostreams::flushable_tag
    {};

    std::vector<std::ostream*> sinks_;

    std::streamsize write(char const* s, std::streamsize n) {
        for(auto sink : sinks_)
            sink->write(s, n);
        return n;
    }

    bool flush() {
        for(auto sink : sinks_)
            sink->flush();
        return true;
    }

    void add_sink(std::ostream& s) {
        sinks_.push_back(&s);
    }

    void remove_sink(std::ostream& s) {
        sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), &s), sinks_.end());
    }
};

int main() {
    MultiSink sink;
    boost::iostreams::stream<MultiSink> stream(sink);
    stream->add_sink(std::cout);
    stream->add_sink(std::cerr);

    stream << "Hello, world!" << std::endl;
}

Note that the code assumes that the registered streams outlive the multi-sink. If that is not the case you need to unregister the streams from the multi-sink before they get destroyed.

like image 91
Maxim Egorushkin Avatar answered Mar 12 '23 19:03

Maxim Egorushkin


You have a few conceptual issues to disentangle:

  1. std::cout is a global object, but std::ostringstream and std::ofstream are types. Discussing them as interchangeable outputs is a category error
  2. std::cout is a global object with program lifetime, but any std::ofstream instance you create may have different lifetime. You need some way to tell whether its lifetime could end before your logger (which isn't a worry with cout, unless your logger also has program lifetime), or to let the logger know that it is responsible for your stream's lifetime.
  3. having a std::vector<std::ostream> streams cannot work, because:
    1. it copies the streams by value, which is explicitly prohibited (see the deleted copy constructor here)
    2. even if it was allowed, it would be broken because of object slicing.

With those out of the way, Maxim's answer is good but doesn't address stream lifetimes - if those aren't a problem (you're happy to statically guarantee every registered stream will outlive the logger), then it's a good solution.

If you do need some extra support to manage object lifetimes, you require something a bit more elaborate - eg. storing proxy objects that know whether or not the logger owns a given stream.

like image 33
Useless Avatar answered Mar 12 '23 18:03

Useless