问题
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:
- The library is cross-platform, so I think using opening
/dev/null
is not a valid solution as it would not work on Windows - The library uses standard C++, so any alternative solutions should not use compiler-specific stuff
回答1:
There 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 exensive:
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);
}
};
...
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
.
回答2:
In this answer you find a general helper to redirect any stream to any other stream:
class stream_redirector {
public:
stream_redirector(std::ios& stream, std::streambuf* newBuf) :
savedBuf_(stream.rdbuf()), stream_(stream)
{
stream_.rdbuf(newBuf);
}
~stream_redirector() {
stream_.rdbuf(savedBuf_);
}
private:
std::streambuf* savedBuf_;
std::ios& stream_;
};
and now all you need is a no-op stream from this answer which discards anything:
template <class cT, class traits = std::char_traits<cT> >
class basic_nullbuf: public std::basic_streambuf<cT, traits> {
typename traits::int_type overflow(typename traits::int_type c)
{
return traits::not_eof(c); // indicate success
}
};
template <class cT, class traits = std::char_traits<cT> >
class basic_onullstream: public std::basic_ostream<cT, traits> {
public:
basic_onullstream():
std::basic_ios<cT, traits>(&m_sbuf),
std::basic_ostream<cT, traits>(&m_sbuf)
{
// note: the original code is missing the required this->
this->init(&m_sbuf);
}
private:
basic_nullbuf<cT, traits> m_sbuf;
};
typedef basic_onullstream<char> onullstream;
typedef basic_onullstream<wchar_t> wonullstream;
and you're good to go:
int main(int argc, char** argv)
{
std::cerr << "foo" << std::endl;
{
onullstream nos;
stream_redirector sr( std::cerr, nos.rdbuf() );
std::cerr << "bar" << std::endl;
}
std::cerr << "baz" << std::endl;
}
and finally here's a live example in case you want to play with it. This method has the additional benefit that you (and your colleagues) can still use std::cerr
in your code and you can turn it off and on again at will :)
来源:https://stackoverflow.com/questions/19200207/redirect-debug-output-to-null-stream-instead-of-stdcerr