views:

67

answers:

3

I have a custom logging class that supports iostream-syntax via a templated operator <<:

template< class T >
MyLoggingClass & operator <<(MyLoggingClass &, const T &) {
    // do stuff
}

I also have a specialized version of this operator that is supposed to be called when a log-message is complete:

template< >
MyLoggingClass & operator <<(MyLoggingClass &, consts EndOfMessageType &){
    // build the message and process it
}

EndOfMessageType is defined like this:

class EndOfMessageType {};
const EndOfMessageType eom = EndOfMessageType( );

The global constant eom is defined so that users can use it just like std::endl at the end of their log-messages. My question is, are there any pitfalls to this solution, or is there some established pattern to do this?

Thanks in advance!

A: 

I think your solution is acceptable. If you wanted to do it differently, you could create a class Message, that would be used instead of the your MyLoggingClass and provided automatic termination.

{
  Message m;
  m << "Line: " << l; // or m << line(l) 
  m << "Message: foo"; // or m << message("foo");
  log << m; // this would automatically format the message
}
jpalecek
@jpalecek: I thought about this, but this would require three lines of code for even the simplest log-messages (and the simple ones are far more frequent than the complex ones).
Space_C0wb0y
A: 

std::endl is a function, not an object, and operator<< is overloaded for accepting a pointer to a function taking and returning a reference to ostream. This overload just calls the function and passes *this.

#include <iostream>

int main()
{
    std::cout << "Let's end this line now";
    std::endl(std::cout); //this is the result of cout << endl, or cout << &endl ;) 
}

Just an alternative to consider.

By the way, I don't think there is any need to specialize the operator: a normal overload does just as well, if not better.

UncleBens
I have never understood why the stream library uses function pointers, though, instead of function objects. But that's probably [also just for historical reasons](http://stackoverflow.com/questions/3222131/why-does-stdbasic-ios-overload-the-unary-logical-negation-operator)...
sbi
A: 

I have done it this way, like some other people did. Have a function Error / Log / Warning / etc that could look like this

DiagnosticBuilder Error( ErrType type, string msg, int line );

This will return a temporary builder object, whose class is basically defined like

struct DiagnosticBuilder {
  DiagnosticBuilder(std::string const& format)
    :m_emit(true), m_format(format) 
  { }
  DiagnosticBuilder(DiagnosticBuilder const& other) 
    :m_emit(other.m_emit), m_format(other.m_format), m_args(other.m_args) {
    other.m_emit = false;
  }
  ~DiagnosticBuilder() {
    if(m_emit) {
      /* iterate over m_format, and print the next arg 
         everytime you hit '%' */
    }
  }

  DiagnosticBuilder &operator<<(string const& s) {
    m_args.push_back(s);
    return *this;
  }
  DiagnosticBuilder &operator<<(int n) {
    std::ostringstream oss; oss << n;
    m_args.push_back(oss.str());
    return *this;
  }
  // ...
private:
  mutable bool m_emit;
  std::string m_format;
  std::vector<std::string> m_args;
};

So if you are building a log message in a loop, be it so

DiagnosticBuilder b(Error("The data is: %"));
/* do some loop */
b << result;

As soon as the builder's destructor is called automatically, the message is emitted. Mostly you would use it anonymously

Error("Hello %, my name is %") << "dear" << "litb";
Johannes Schaub - litb