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:
std::cout
is a global object, but std::ostringstream
and std::ofstream
are types. Discussing them as interchangeable outputs is a category errorstd::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.std::vector<std::ostream> streams
cannot work, because:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With