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 error 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. 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.
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.