Search code examples
c++stloperator-overloadingiostreamiomanip

How to code as the style like STL IO manipulator functions?


I'm developing a log lib for myself, and want it can be used in a way like the style of iostream. For example:

log_debug << "Log body strings..." << endlog;

instead of:

log_debug( "Log body strings..." );

My codes is here:

class Log_t {
public:
   friend Log_t& endlog( Log_t& rp_Logger );
   friend Log_t& operator<<( Log_t& rp_Logger, const char* p_pchBody );
private:
   std::stringstream m_ssBuffer;
};

Log_t& endlog( Log_t& rp_Logger ) {
   std::cout << rp_Logger.m_ssBuffer.str() << std::endl;
   rp_Logger.m_ssBuffer = std::stringstream();

   return rp_Logger;
};

Log_t& operator<<( Log_t& rp_Logger, const char* p_pchBody ) {
   rp_Logger.m_ssBuffer << p_pchBody;
   return rp_Logger;
};

int main() {
   Log_t log;

   log << "Hello Logger!" << endlog;
   return EXIT_SUCCESS;
};
  1. These codes can not pass through compilation, and I got "no match for ‘operator<<’ (operand types are ‘Log_t’ and ‘Log_t&(Log_t&)’)".

  2. I can't found a way to tell out the end of a single log, while with the style of calling-a-function, it is not a problem.

As calling-a-function: log_debug( "Log body strings..." );, the end of a log has been impied by calling. --One calling, One log line-- But in the style with "<<" overloading, we can not tell where is the end of one individual log, because follows should be valid too:

log_debug << "Log " << "body " << "goes " << "here...\n"
          << "the rest of the same log goes here."
          << endlog;

It is why I coded a function "endlog()", neither for insertint a "\n" character, nor for flushing IO, rather in order to tell out "here is an end of one single log".

Would anyone please help me? Sorry for my poor English.


Solution

  • Your problem is that streams are non copyable:

    // In C++03 this is a copy and not allowed.
    rp_Logger.m_ssBuffer = std::stringstream();
    

    In C++11 and later this is allowed as it becomes a move operation. But there is a better way to expresses this:

    // You want to clear the stream
    rp_Logger.m_ssBuffer.str("");
    

    The next problem is that you have not overloaded the operator<< for functions only for C-Strings.

    So we need to define operator<< so that you can pass functions and they get called. So you can do this.

     Log_t& operator<<( Log_t& rp_Logger, std::function<Log_t&(Log_t&)>&& action)
     {
         return action(rp_Logger);
     }
    

    That should solve your compilation problems.

    But there is what I would consider a design issue here. Presumably you can turn your logging on/off (more less verbose) something like that (most logging systems have this ability).

    The problem here is that even when the logging system is inactive you will still get a call for every operator<< in the chain which could be a bit inefficient if it is not logging anything.

    Also every parameter needs to be evaluated. Which can potentially be expensive especially if those parameters are then simply thrown away when the logging level is turned down.

    log << "Error: " << expensiveCallToGetState() << " Line 10: " << anotherCallToGetHumanString() << endl;
    

    Here we have 5 calls to operator<< and both function calls must be evaluated before the call.