A software library that I am working with writes a lot of debug output to std::cerr
, but redirects that output to a null stream if I tell it to be quiet. This is a simplified main.cpp
that shows how the code tries to achieve this:
#include <iostream>
#include <fstream>
#include <cassert>
// The stream that debug output is sent to. By default
// this points to std::cerr.
std::ostream* debugStream(&std::cerr);
// Throughout the library's codebase this function is called
// to get the stream that debug output should be sent to.
std::ostream& DebugStream()
{
return *debugStream;
}
// Null stream. This file stream will never be opened and acts
// as a null stream for DebugStream().
std::ofstream nullStream;
// Redirects debug output to the null stream
void BeQuiet()
{
debugStream = &nullStream;
}
int main(int argc, char** argv)
{
DebugStream() << "foo" << std::endl;
BeQuiet();
DebugStream() << "bar" << std::endl;
assert(debugStream->good());
return 0;
}
When you run this program, you will notice that the string "bar" is correctly sent to the null stream. However, I noticed that the assertion fails. Is this something that I should be concerned about? Or is this just a slightly ugly detail of the approach chosen by the library developers?
If you feel like it, suggestions for better alternatives are welcome. Some constraints:
/dev/null
is not a valid solution as it would not work on WindowsThere is no real need to be worried about the stream not being good()
! Since the output operators don't really do anything wirh a stream in failure mode the different entities being logged are not formatted, i.e., the code does run faster compared to alternative approaches.
Note that you don't really need a second stream to disable output:
Assuming all output operators are well-behaved, you can just set std::ios_base::failbit
:
debugStream().setstate(std::ios_base::failbit);
If there are misbehaved output which write to a stream even if it isn't good()
you can just set its stream buffer to null:
debugStream().rdbuf(nullptr);
If you really want your stream to remain in good()
state, you'd install a stream buffer which just consumes characters. Note, however, that you want to give this stream buffer a buffer as having overflow()
called for each char
is fairly expensive. Also, overriding xsputn()
is a benefit:
struct nullbuf
: std::streambuf {
char buf[256];
int overflow(int c) {
this->setp(this->buf, this->buf + 256);
return std::char_traits<char>::not_eof(c);
}
std::streamsize xsputn(char const* , std::streamsize n) override {
return n;
}
};
...
nullbuf sbuf;
debugStream().rdbuf(&sbuf);
...
debugStream().rdbuf(0);
It is necessary to reset the stream's stream buffer because the destructor of an std::ostream
will flush the stresm buffer (i.e., it calls pubsync()
). Doing so on a destroyed stream buffer won't work.
Personally, I would go with setting std::ios_base::failbit
.