33

I'm looking at making a logging class which has members like Info, Error etc that can configurably output to console, file, or to nowhere.

For efficiency, I would like to avoid the overhead of formatting messages that are going to be thrown away (ie info messages when not running in a verbose mode). If I implement a custom std::streambuf that outputs to nowhere, I imagine that the std::ostream layer will still do all the formatting. Can anyone suggest a way to have a truly "null" std::ostream that avoids doing any work at all on the parameters passed to it with <<?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
jcs
  • 343
  • 3
  • 6
  • i would not worry. just use a null stream like shown by neil. the class does not need any better performance, because obviously if you don't have a null target, formatting *has* to be done, so it's obviously not critical. just my 2 cents – Johannes Schaub - litb Apr 17 '09 at 13:18
  • hmm, but it looks like it is intended as a "debug output" thingy? one way i've seen is like this: out() << a << b...; and out() returns struct f { }; with out being template f const& operator<<(f const& f_, T const) { return f_; }, and then make out return different structs depending on the log level. or make different out functions or whatever. – Johannes Schaub - litb Apr 17 '09 at 13:36

5 Answers5

16

A swift google came up with this example which may be of use. I offer no guarantees, except that it compiles and runs :-)

#include <streambuf>
#include <ostream>

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)
        {
            init(&m_sbuf);
        }

    private:
        basic_nullbuf<cT, traits> m_sbuf;
};

typedef basic_onullstream<char> onullstream;
typedef basic_onullstream<wchar_t> wonullstream;

int main() {
    onullstream os;
    os << 666;
}
  • +1. Yes, I guess that deriving from std::basic_ostream<> is necessary if you want to pass a do-nothing stream into a function expecting an ostream& or ostream* parameter -- Iraimbilanja's trick won't work there. – j_random_hacker Apr 17 '09 at 15:01
  • it could only be accepted as `ostream *`, not `ostream &`, corrrect? – athos Oct 17 '16 at 09:00
  • @athos `std::ostream` is an *alias* for `std::basic_ostream>`. This class publicly inherits from that – Caleth Feb 03 '21 at 15:12
9

all, thanks for sharing the code, I just do a test, then Neil's method will still do the string formating, for example:

#include <streambuf>
#include <ostream>
#include <iostream>
using namespace std;


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)
        {
            init(&m_sbuf);
        }

    private:
        basic_nullbuf<cT, traits> m_sbuf;
};

typedef basic_onullstream<char> onullstream;
typedef basic_onullstream<wchar_t> wonullstream;

class MyClass
{
    int a;
    friend ostream& operator<< (ostream&, MyClass const&);
};

ostream& operator<<(ostream& out,MyClass const& b)
{
    std::cout<<"call format function!!";
    out << b.a;
    return out;
}

int main() {
    onullstream os;
    MyClass obj;
    os<<obj;
}

Running this program, you will find that "ostream& operator<<(ostream& out,MyClass const& b)" will be called. So, doing format on the obj will still be called. So, we still can't avoid the overhead of formatting messages.

ollydbg23
  • 1,124
  • 1
  • 12
  • 38
  • AFAIK the optimiser can remove code which is deems has no side effects. Since your call to `std::cout << "call format function!!"` does have side effects, then the optimiser won't remove this. However, without the call, it is possible this will be removed – Steve Lorimer May 22 '12 at 03:21
4

To prevent the operator<<() invocations from doing formatting, you should know the streamtype at compile-time. This can be done either with macros or with templates.

My template solution follows.

class NullStream {
public:
    void setFile() { /* no-op */ }
    template<typename TPrintable>
    NullStream& operator<<(TPrintable const&)
    { return *this; } /* no-op */
}

template<class TErrorStream> // add TInfoStream etc
class Logger {
public:
    TErrorStream& errorStream() {
        return m_errorStream;
    }

private:
    TErrorStream m_errorStream;
};

//usage
int main() {
    Logger<std::ofstream> normal_logger; // does real output
    normal_logger.errorStream().open("out.txt");
    normal_logger.errorStream() << "My age is " << 19;

    Logger<NullStream> null_logger; // does zero output with zero overhead
    null_logger.errorStream().open("out.txt"); // no-op
    null_logger.errorStream() << "My age is " << 19; // no-op
}

Since you have to do this at compile-time, it is of course quite inflexible.

For example, you cannot decide the logging level at runtime from a configuration file.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
  • +1: simple, clean, does the job well. Note that e.g. "NullStream s; s << expensive_function();" will most likely still evaluate expensive_function(), especially if it lives in another module. – j_random_hacker Apr 17 '09 at 14:59
  • 3
    I'll also mention that, unlike Neil's onullstream, your NullStream can't be passed to a function expecting an ostream& or ostream* argument. – j_random_hacker Apr 17 '09 at 15:04
  • expensive_function() will _definitely_ be evaluated no matter where it lives. There is no way to pervent that barring macros and conditional compilation :) ... as to Neil's onullstream, I do not believe it fulfils the "zero formatting overhead" requirement :) –  Apr 17 '09 at 16:30
  • That's true, Neil's has a different tradeoff. I suppose your class could offer a conversion function to ostream& that returns an instance of Neil's class to get the best of both worlds ;) – j_random_hacker Apr 18 '09 at 15:44
0

Probably you'll need more than just text formatting and message filtering. What about multithreading?

I would implement the filtering and multithreading synchronization as the responsibility of a separate class.

However, logging is a not-so-simple problem, and I would try to use existing logging solutions, instead of developing a new one.

Cătălin Pitiș
  • 14,123
  • 2
  • 39
  • 62
0

Why not using existing logging solutions used by millions of users? log4j, log4net, log4cxx.., to name just a few..

Dima
  • 4,068
  • 4
  • 38
  • 47