简体   繁体   中英

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.

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 stream s 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.

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.

You can't store copies of your streams, they are not copiable. You have to store std::reference_wrapper s or pointers instead.

class logger {
    std::vector<std::reference_wrapper<std::ostream>> streams;
public:
    void register_stream(std::ostream &stream) {
        streams.emplace_back(stream);
    }

    template <typename T>
    void operator<<(T const &value) {
        for (auto &stream : streams) { // note the '&'
            stream.get() << value;
        }
    }
};

or if you want to be able to chain your calls like l << "log" << " this"; :

    template <typename T>
    logger & operator<<(T const &value) {
        for (auto &stream : streams) {
            stream.get() << value;
        }
        return *this;
    }

"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." – Useless

The current solution to this problem in my current project looks much like this:

using ostream_deleter = std::function<void(std::ostream *)>;
using ostream_ptr = std::unique_ptr<std::ostream, ostream_deleter>;

This allows you to store a new stream object, with ownership, eg

ostream_deleter d{std::default_delete<std::ostream>{}};
ostream_ptr fileStream{new std::ofstream{"/tmp/example.foo"}, std::move(d)};

Note that you should create the type-erased deleter first for reasons of exception safety.

It also allows you to use global streams which are known to outlive your logger:

ostream_ptr coutStream{&std::cout, [](std::ostream &) {}};

There is also a null_deleter in Boost if you want more of a self-documenting syntax.

Unfortunately, there's still an issue with streams that may be internally redirected . Indeed, C++ supports redirecting the output to (or input from) any stream to a different stream buffer, eg

std::ofstream logStream{"/tmp/my.log"};
const auto oldBuf = std::cout.rdbuf(logStream.rdbuf());
std::cout << "Hello World.\n"; // output redirected to /tmp/my.log
std::cout.rdbuf(oldBuf); // undo redirection

The issue here is that the lifetime of the stream buffer is tied to logStream , not std::cout . This complicates any lifetime management rsp. makes a fully general solution about impossible. Discipline and convention should still help a lot, of course, and admittedly stream redirection is a fairly obscure and rarely used feature.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM