Earlier,
I said you
almost
always want to use
inline
functions instead of preprocessor macros. The exceptions are when you need to
use three special features in the C preprocessor (which is, by inheritance, the
C++ preprocessor): stringizing,
string concatenation, and
token pasting.
Stringizing, performed with the
#
directive, allows you to take an identifier and turn it into a string, whereas
string concatenation takes place when two adjacent strings have no intervening
punctuation, in which case the strings are combined. These two features are
exceptionally useful when writing debug code. Thus,
#define
DEBUG(X) cout << #X " = " << X << endl
This
prints the value of any variable. You can also get a trace that prints out the
statements as they execute:
#define
TRACE(S) cout << #S << endl; S
The
#S
stringizes the statement for output, and the second
S
reiterates the statement so it is executed. Of course, this kind of thing can
cause problems, especially in one-line
for
loops:
for(int i = 0; i < 100; i++)
TRACE(f(i));
Because
there are actually two statements in the
TRACE( )
macro, the one-line
for
loop executes only the first one. The solution is to replace the semicolon with
a comma in the macro.
Token
pasting
Token
pasting is very useful when you are manufacturing code. It allows you to take
two identifiers and paste them together to automatically create a new
identifier. For example,
#define FIELD(A) char* A##_string; int A##_size
class Record {
FIELD(one);
FIELD(two);
FIELD(three);
// ...
};
Each
call to the
FIELD( )
macro creates an identifier to hold a string and another to hold the length of
that string. Not only is it easier to read, it can eliminate coding errors and
make maintenance easier. Notice, however, the use of all upper-case characters
in the name of the macro. This is a helpful practice because it tells the
reader this is a macro and not a function, so if there are problems, it acts as
a little reminder.