February 06, 2003
std::ostream & operator<<

I have become quite a fan of defining inserter (operator<<) methods for my C++ classes. Output becomes so simple just: os << instance_of_my_class. I rewrote my program logging to use an ogzstream class and custom inserters for "almost everything". When I run into problems I just have to add a short log statement and see the results. I would always run in to problems using the Visual C++ TRACE macros which use a printf type of format string. There's a (quite small) limit on the string buffer which causes an ASSERT if it overflows. And it isn't typesafe whereas with operator<< you can output almost anything and if the compiler can't find a << operator for a class it will complain. TRACE will merrily compile anything and then ASSERT or crash if the item isn't a CString or a simple type like int or double. The debug and logging macros I use are shown below. The CError::stream() and CError::gzlog() are static methods which define static variables internally so that I can do logging and stream formatting at any time during my program — even during static class variable initialization. In effect I'm using singletons.

// Usage: GLOBAL_MSG(FILE_LINE, fmt, arguments...) ;
#define FILE_LINE THIS_FILE, __LINE__
#define GLOBAL_MSG CError::global_msg
// Pre-format an inserter expression msg with a date-time stamp, a keyword,
// the source code file and the line number.
#define INSERTMSG(keyword,msg) \
  CError::now() \
  << " " << keyword << " " \
  << CSplitPath::get_filename(THIS_FILE) \
  << ":" << __LINE__ \
  << " " << msg
// Usage: LOGMSG("result", "The result is " << result) ;
#define LOGMSG(keyword,msg) CError::gzlog() << INSERTMSG(keyword,msg) \
  << std::endl
//
#define POPUPMSG(x) CError::stream() << x ; \
  CError::popup_msg(CError::stream().str().c_str(), 0, \
                    CSplitPath::get_filename(THIS_FILE).c_str(), __LINE__) ; \
  CError::stream().str("")
#ifdef _DEBUG
// Fancy macros to generate debug or trace listing in the log file as well
// as a trace message. It is wrapped in a CCriticalSection because TRACEZ
// messages may come from different execution threads and the CError::stream()
// is shared among them. There are two std::endl, one to flush and send the
// line to the log file and the other to new line terminate the string.
//
// The if ... TRACEBUFSIZE works around a TRACE0 bug as TRACE0(string)
// resolves to AfxTrace("%s", string) and there's a 4096 character buffer
// limit which causes an assert if string is more than this number of
// characters.
//
// Arguments: (keyword,expression,inserter)
//   keyword - a string e.g. "trace"
//   expression - if (expression) is true send inserter to file and debug
//                output otherwise just use TRACE0
//   inserter - C++ << expression
//
// Usage: TRACEMSG("trace",1,"The value is " << value) ;
//
// Reasons:
//   1) You can't imbed inserters in function calls though I have tried. :-)
//   2) Uses TRACE0 so that no formatting is done. In some cases formatting
//      may overflow whatever buffer visual C++ provides causing an assert
//      which isn't related to the program.
//   3) Completely disappears when _DEBUG not defined.
# define TRACEBUFSIZE 230
# define TRACEMSG(keyword,logfile,x) do \
  { \
    CriticalSectionLock cs_lock ; \
    CError::stream() << INSERTMSG(keyword,x) ; \
    if (logfile) \
    { \
      CError::gzlog() << CError::stream().str() << std::endl ; \
    } \
    if (CError::stream().str().size() > TRACEBUFSIZE) \
    { \
      std::string str(CError::stream().str(), 0, TRACEBUFSIZE) ; \
      str += "...\n" ; \
      TRACE0(str.c_str()) ; \
    } \
    else \
    { \
      CError::stream() << std::endl ; \
      TRACE0(CError::stream().str().c_str()) ; \
    } \
    CError::stream().str("") ; \
  } while(0)
# define TRACEZ(x) TRACEMSG("trace",1,x)
// Trace with no file logging.
# define TRACENZ(x) TRACEMSG("trace",0,x)
# define TRACEIF(condition,x) if (condition) \
{ \
  TRACEMSG("trace",1,x) ; \
}
# define TRACEIFELSE(condition,x,y) if (condition) \
{ \
  TRACEMSG("trace",1,x) ; \
} else { \
  TRACEMSG("trace",1,y) ; \
}
// Log info messages to trace output as well as log file.
# define INFOMSG(x) TRACEMSG("info",1,x)
# define WARN(x) TRACEMSG("WARN",1,x)
# if defined(ENABLE_VERBOSE) && ENABLE_VERBOSE
#   define VERBOZE(x) TRACEMSG("verbose",1,x)
# else // ENABLE_VERBOSE
#   define VERBOZE(x)
# endif // ENABLE_VERBOSE
#else // _DEBUG
// Some macros now cause their arguments to disappear and insert nothing.
# define TRACEZ(x)
# define TRACENZ(x)
# define INFOMSG(x) LOGMSG("info",x)
# define WARN(x) LOGMSG("WARN",x)
# define TRACEIF(condition,x)
# define TRACEIFELSE(condition,x,y)
# define TRACEMSG(keyword,x)
# define VERBOZE(x)
#endif // _DEBUG
#define GZLOG(x) INFOMSG(x)
 
Posted by jservice at February 06, 2003 09:50 PM
Comments