File: why_macro.dox

package info (click to toggle)
libcwd 1.0.4-1.1
  • links: PTS
  • area: non-free
  • in suites: jessie, jessie-kfreebsd
  • size: 8,136 kB
  • ctags: 10,313
  • sloc: cpp: 23,354; sh: 9,798; ansic: 1,172; makefile: 852; exp: 234; awk: 11
file content (123 lines) | stat: -rw-r--r-- 4,795 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/*!

\page page_why_macro Design Consideration Concerning Macros

This page describes why we use a macro instead of an inline
function for <CODE>Dout()</CODE>.

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

Using a macro has its advantages however:

<OL>
  <LI>It inlines the debug code even when we don't use optimization.
  <LI>It allows us to use tricks that make the code run faster when debug code is included.
  <LI>It allows us to omit variables in the program that are only used for debugging and which are written to a debug <CODE>ostream</CODE>.
  <LI>It compiles faster when debugging is omitted.
  <LI>No optimization is needed to really get rid of the debug code when debugging is omitted.
</OL>

Points 1, 2 and 3 are the most important reasons that lead to the decision to use a macro.&nbsp;
Please note that  the author of %libcwd used the alternative for <B>two years</B> before finally deciding
to <B>rewrite</B> the debug facility, being convinced that it was better to do it the way it is done now.&nbsp;
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.&nbsp;
In most cases the debug code will be compiled and used <em>without</em> compiler optimization; implying
the fact that no inlining is done.&nbsp;
Moreover, we expect to use a lot of inserter operators and without
optimization, each of these will be called.&nbsp;
[ Note that when omitting debug code we can't get rid of the content of all
<CODE>%operator<<</CODE> 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 <CODE>ostream</CODE> (let's say to a class <CODE>no_dstream</CODE>).&nbsp;
Each call to <CODE>template<class T> no_dstream %operator<<(T) { };</CODE> would actually be done(!),
without inlining (point&nbsp;5&nbsp;above)&nbsp;].

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

Let's start with writing some example debug output to <CODE>cerr</CODE>.

\code
std::cerr << "i = " << i << "; j = " << j << "; s = " << s << std::endl;
\endcode

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

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

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

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

\code
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;
\endcode

checking <CODE>on</CODE> seven times.

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

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

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

Example:

\code
  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;
  }
\endcode

*/