libcwd: The C++ Debugging Support Library - Reference Manual
Author: Carlo Wood
Description: C++ debugging support library with ostream debug output
Keywords: libcwd, debug output, debug facility, debug channel, object orientation, OO, C++, nested debug output, ostream, memory allocation, debugging, source file and line number

Main Page Reference Manual Namespace List Compound List Namespace Members Compound Members File Members

# Design Consideration Concerning Macros

This page describes why we use a macro instead of an inline function for `Dout()`.

Good C++ code whenever possible, should not use macros but `inline` functions which have the advantage of type checking, overloading and easier debugging with a debugger. It is therefore a very relevant question to ask why a macro was used for `Dout()`.

Using a macro has its advantages however:

1. It inlines the debug code even when we don't use optimization.
2. It allows us to use tricks that make the code run faster when debug code is included.
3. It allows us to omit variables in the program that are only used for debugging and which are written to a debug `ostream`.
4. It compiles faster when debugging is omitted.
5. No optimization is needed to really get rid of the debug code when debugging is omitted.

Points 1, 2 and 3 are the most important reasons that lead to the decision to use a macro. Please note that the author of libcwd used the alternative for two years before finally deciding to rewrite the debug facility, being convinced that it was better to do it the way it is done now. While points 4 and 5 are trivial, the first three advantages might need some explanation:

1. Usually a developer won't use compiler optimization because that makes debugging harder. In most cases the debug code will be compiled and used without compiler optimization; implying the fact that no inlining is done. Moreover, we expect to use a lot of inserter operators and without optimization, each of these will be called. [ Note that when omitting debug code we can't get rid of the content of all `operator<<` functions because they might be used for non-debug code too, so we'd need to use a trick and write to something else than an `ostream` (let's say to a class `no_dstream`). Each call to `template<class T> no_dstream operator<<(T) { };` would actually be done(!), without inlining (point 5 above) ].

2. Let's develop step by step an example where we write debug output.

Let's start with writing some example debug output to `cerr`.

    std::cerr << "i = " << i << "; j = " << j << "; s = " << s << std::endl;

This line calls seven functions. If we want to save CPU time when we don't want to write this output, then we need to test whether the debugging output (for this specific channel) is turned on or off before calling the `operator<<()`'s. After all, such an operator call can use a lot of CPU time for arbitrary objects.

We cannot pass `"i = " << i << "; j = " << j << "; s = " << s << std::endl` to an inline function without causing all `operator<<` functions to be called. The only way, not using a macro, to achieve that no `operator<<` is called is by not calling them in the first place: We can't write to an `ostream`. It is necessary to write to a new class (let's call that class `dstream`) which checks if the debug channel we are writing to is turned on before calling the `ostream operator<<()`:

    template<class T>
      inline dstream&
      operator<<(dstream& ds, T const& data)
      {
        if (on)
          std::cerr << data;
      }

Nevertheless, even with inlining (often requiring the highest level of optimization), most compilers would turn that into:

    if (on)
      std::cerr << "i = ";
    if (on)
      std::cerr << i;
    if (on)
      std::cerr << "; j = ";
    if (on)
      std::cerr << j;
    if (on)
      std::cerr << "; s = ";
    if (on)
      std::cerr << s;
    if (on)
      std::cerr << std::endl;

checking `on` seven times.

With a macro we can easily achieve the best result, even without any optimization:

    if (on)
      std::cerr << "i = " << i << "; j = " << j << "; s = " << s << std::endl;

3. Sometimes a variable is specific to debug code and only being used for writing to a debug ostream. When the debug is omitted and inline functions are used for the `Dout()` calls, then these variables still need to exist: they are passed to the `Dout()` function (or actually, to the `no_dstream operator<<()`'s). Using a macro allows one to really get rid of such variables by surrounding them with `#ifdef CWDEBUG ... #endif` preprocessor directives.

Example:

      if (need_close)
      {
        Dout(dc::system|continued_cf, "close(" << __fd << ") = ");
    #ifdef CWDEBUG
        int ret =
    #endif
        ::close(__fd);
        Dout(dc::finish|cond_error_cf(ret < 0), ret);
        return false;
      }