The real problem is that defines are handled by a different tool from the rest of the language (the preprocessor). As a consequence, the compiler doesn’t know about it, and cannot help you when something goes wrong – such as reuse of a preprocessor name.
Consider the case of max
which is sometimes implemented as a macro. As a consequence, you cannot use the identifier max
anywhere in your code. Anywhere. But the compiler won’t tell you. Instead, your code will go horribly wrong and you have no idea why.
Now, with some care this problem can be minimised (if not completely eliminated). But for most uses of #define
there are better alternatives anyway so the cost/benefit calculation becomes skewed: slight disadvantage for no benefit whatsoever. Why use a defective feature when it offers no advantage?
So here is a very simple diagram:
- Need a constant? Use a constant (not a define)
- Need a function? Use a function (not a define)
- Need something that cannot be modelled using a constant or a function? Use a define, but do it properly.
Doing it “properly” is an art in itself but there are a few easy guidelines:
Use a unique name. All capitals, always prefixed by a unique library identifier. max
? Out. VERSION
? Out. Instead, use MY_COOL_LIBRARY_MAX
and MY_COOL_LIBRARY_VERSION
. For instance, Boost libraries, big users of macros, always use macros starting with BOOST_<LIBRARY_NAME>_
.
Beware of evaluation. In effect, a parameter in a macro is just text that is replaced. As a consequence, #define MY_LIB_MULTIPLY(x) x * x
is broken: it could be used as MY_LIB_MULTIPLY(2 + 5)
, resulting in 2 + 5 * 2 + 5
. Not what we wanted. To guard against this, always parenhesise all uses of the arguments (unless you know exactly what you’re doing – spoiler: you probably don’t; even experts get this wrong alarmingly often).
The correct version of this macro would be:
#define MY_LIB_MULTIPLY(x) ((x) * (x))
But there are still plenty of ways of getting macros horribly wrong, and, to reiterate, the compiler won’t help you here.