Search code examples
c++c++11operator-overloading

<< operator overloading in C++ for logging purposes


I have a C++ class where I place many std::cout statements to print informative text messages about a mass of signals that this class is handling. My intentition is to redirect these text messages to a function named log. In this function, I have flag named mVerbose which defines if the log text should be printed. The content of this function is as follows:

 void XXXProxy::log(std::stringstream& ss)
 {
   if(mVerbose)
   {
     std::cout << ss;
     ss << "";
   }
 }

Then, the caller code snippet to this function is as follows: std::stringstream logStr;

 logStr << "SE"
        << getAddr().toString()
        << ": WAITING on epoll..."
        << std::endl;      
 log(logStr);

I would like to overload the << operator in my XXXProxy in a way that I can get rid of creating a std::stringstream object and calling the log function. I want to be able to log the text messages as below and let the << operator aggregate everything into:

 << "SE"
 << getAddr().toString()
 << ": WAITING on epoll..."
 << std::endl;      

So I wouldlike to have an member << function that looks like:

 void XXXProxy::operator << (std::stringstream& ss)
 {
   if(mVerbose)
   {
     std::cout << ss;
     ss << "";
   }
 } 

QUESTION

I am relatively a novice C++ developer and get lots of compilation errors when attemting to write the above stated like << operator. Could you please make some suggestions or direct me to some links for me to correctly implement this << operator. Thanks.


Solution

  • If you don't want to use std::cout directly and you want to have your own Log class, you could implement a simple wrapper providing the same interface of std::ostream: operator<<:

    class Log {
    private: 
        std::ostream& _out_stream;
     
        //Constructor: User provides custom output stream, or uses default (std::cout).
        public: Log(std::ostream& stream = std::cout): _out_stream(stream) {} 
     
        //Implicit conversion to std::ostream
        operator std::ostream() {
            return _out_stream;
        } 
    
        //Templated operator>> that uses the std::ostream: Everything that has defined 
        //an operator<< for the std::ostream (Everithing "printable" with std::cout 
        //and its colleages) can use this function.    
        template<typename T> 
        Log& operator<< (const T& data) 
        {
            _out_stream << data;
            return *this;
        }
    }
    

    So if you implement std::ostream& operator>>(std::ostream& os , const YourClass& object) for your classes, you can use this Log class.

    The advantage of this approach is that you use the same mechanism to make std::cout << your_class_object work, and to make the class work with the Log.

    Example:

    struct Foo
    {
        int x = 0; //You marked your question as C++11, so in class initializers 
                   //are allowed. 
    
        //std::ostream::operator<< overload for Foo:
        friend std::ostream& operator<<(std::ostream& os , const Foo& foo)
        {
            return os << foo.x;
        }
    };
    
    int main()
    {
      Log my_log;
      Foo my_foo;
    
      my_foo.x = 31415;
    
      my_log << my_foo << std::endl; //This prints "31415" using std::cout.
    }
    

    Possible improvements:

    • You could write a extern const of class Log, and make the class implement a singleton. This allows you to access the Log everywhere in your program.
    • It's common in log outputs to have a header, like Log output (17:57): log message. To do that, you could use std::endl as a sentinel and store a flag that says when the next output is the beginning of a line (the beginning of a log message). Checkout the next answer for a complete and working implementation.

    References: