Redirect debug output to null stream instead of std::cerr

北慕城南 提交于 2020-01-04 03:47:12

问题


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:

  1. Assuming all output operators are well-behaved, you can just set std::ios_base::failbit:

    debugStream().setstate(std::ios_base::failbit);
    
  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!