views:

271

answers:

5

I've implemented an ostream for debug output which sends ends up sending the debug info to OutputDebugString. A typical use of it looks like this (where debug is an ostream object):

debug << "some error\n";

For release builds, what's the least painful and most performant way to not output these debug statements?

+3  A: 

The most common (and certainly most performant) way is to remove them using the preprocessor, using something like this (simplest possible implementation):

#ifdef RELEASE
  #define DBOUT( x )
#else
  #define DBOUT( x )  x
#endif

You can then say

DBOUT( debug << "some error\n" );

Edit: You can of course make DBOUT a bit more complex:

#define DBOUT( x ) \
   debug << x  << "\n"

which allows a somewhat nicer syntax:

DBOUT( "Value is " << 42 );

A second alternative is to define DBOUT to be the stream. This means that you must implement some sort of null stream class - see http://stackoverflow.com/questions/760301/implementing-a-no-op-stdostream/760353#760353. However, such a stream does have an runtime overhead in the release build.

anon
I was hoping there was some way that retained the nice iostream syntax and also optimized the statements away in release builds like the macros do.
Emanuel
+2  A: 

How about this? You'd have to check that it actually optimises to nothing in release:

#ifdef NDEBUG
    class DebugStream {};
    template <typename T>
    DebugStream &operator<<(DebugStream &s, T) { return s; }
#else
    typedef ostream DebugStream;
#endif

You will have to pass the debug stream object as a DebugStream&, not as an ostream&, since in release builds it isn't one. This is an advantage, since if your debug stream isn't an ostream, that means you don't incur the usual runtime penalty of a null stream that supports the ostream interface (virtual functions that actually get called but do nothing).

Warning: I just made this up, normally I would do something similar to Neil's answer - have a macro meaning "only do this in debug builds", so that it is explicit in the source what is debugging code, and what isn't. Some things I don't actually want to abstract.

Neil's macro also has the property that it absolutely, definitely, doesn't evaluate its arguments in release. In contrast, even with my template inlined, you will find that sometimes:

debug << someFunction() << "\n";

cannot be optimised to nothing, because the compiler doesn't necessarily know that someFunction() has no side-effects. Of course if someFunction() does have side effects then you might want it to be called in release builds, but that's a peculiar mixing of logging and functional code.

Steve Jessop
Thanks! I was starting to think something along these lines myself and I'm glad to see I'm not the only one that was thinking it. I'll try it out on Monday back at work and see how well the compiler is able to optimize the stream away.
Emanuel
+2  A: 

Like others have said the most performant way is to use the preprocessor. Normally I avoid the preprocessor, but this is about the only valid use I have found for it bar protecting headers.

Normally I want the ability to turn on any level of tracing in release executables as well as debug executables. Debug executables get a higher default trace level, but the trace level can be set by configuration file or dynamically at runtime.

To this end my macros look like

#define TRACE_ERROR if (Debug::testLevel(Debug::Error)) DebugStream(Debug::Error)
#define TRACE_INFO  if (Debug::testLevel(Debug::Info))  DebugStream(Debug::Info)
#define TRACE_LOOP  if (Debug::testLevel(Debug::Loop))  DebugStream(Debug::Loop)
#define TRACE_FUNC  if (Debug::testLevel(Debug::Func))  DebugStream(Debug::Func)
#define TRACE_DEBUG if (Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug)

The nice thing about using an if statement is that there is no cost to for tracing that is not output, the tracing code only gets called if it will be printed.

If you don't want a certain level to not appear in release builds use a constant that is available at compile time in the if statement.

#ifdef NDEBUG
    const bool Debug::DebugBuild = false;
#else
    const bool Debug::DebugBuild = true;
#endif

    #define TRACE_DEBUG if (Debug::DebugBuild && Debug::testLevel(Debug::Debug)) DebugStream(Debug::Debug)

This keeps the iostream syntax, but now the compiler will optimise the if statement out of the code, in release builds.

iain
The use of if statements isn't a bad idea! I know that if statements in macros may have some pitfalls so I'd have to be especially careful constructing them and using them. For example:if (error) TRACE_DEBUG << "error";else do_something_for_success();Would end up executing do_something_for_success() if an error occurs and debug-level trace statements are disabled because the else statement binds with the inner if-statement. However, most coding styles mandate use of curly braces which would solve the problem.if (error) { TRACE_DEBUG << "error";}else do_something_for_success();
Emanuel
Safest way to do a macro is to wrap it inside do{ macro_here } while(0);
Zan Lynx
+1  A: 

@iain: Ran out of room in the comment box so posting it here for clarity.

The use of if statements isn't a bad idea! I know that if statements in macros may have some pitfalls so I'd have to be especially careful constructing them and using them. For example:

if (error) TRACE_DEBUG << "error";
else do_something_for_success();

...would end up executing do_something_for_success() if an error occurs and debug-level trace statements are disabled because the else statement binds with the inner if-statement. However, most coding styles mandate use of curly braces which would solve the problem.

if (error) 
{
    TRACE_DEBUG << "error";
}
else
{
    do_something_for_success();
}

In this code fragment, do_something_for_success() is not erroneously executed if debug-level tracing is disabled.

Emanuel
@Emanuel, yes you have to be careful of this, I always use brackets for my if statements and for loops. I have seen bugs where people added a new line to the then part of an if statement but forgot to add the curlies, the code was nicely indented so bug was almost impossible to spot.
iain
+1  A: 
#ifdef RELEASE
  #define DBOUT( x )
#else
  #define DBOUT( x )  x
#endif

Just use this in the actual ostream operators themselves. You could even write a single operator for it.

template<typename T> Debugstream::operator<<(T&& t) {
    DBOUT(ostream << std::forward<T>(t);) // where ostream is the internal stream object or type
}

If your compiler can't optimize out empty functions in release mode, then it's time to get a new compiler.

I did of course use rvalue references and perfect forwarding, and there's no guarantee that you have such a compiler. But, you can surely just use a const ref if your compiler is only C++03 compliant.

DeadMG