What is the correct strategy to limit the scope of #define
labels and avoid unwarranted token collision?
In the following configuration:
The correct strategy would be to not use
#define ZERO '0'
#define ONE '1'
at all. If you need constant values, use, in this case, a const char
instead, wrapped in a namespace.
There are two types of #define
Macros:
One which are need only in a single file. Let's call them Private #defines
eg. PI 3.14
In this case:
As per the standard practice: the correct strategy is to place #define
labels - in only the implementation, ie. c
, files and not the header h
file.
Another that are needed by multiple files: Let's call these Shared #defines
eg. EXIT_CODE 0x0BAD
In this case:
Place only such common #define
labels in header h
file.
Additionally try to name labels uniquely with False NameSpaces
or similar conventions like prefixing the label with MACRO_
eg: #define MACRO_PI 3.14
so that the probability of collision reduces
Some options:
Use different capitalization conventions for macros vs. ordinary identifiers.
const UINT Zero = 0;
Fake a namespace by prepending a module name to the macros:
#define UTIL_ZERO '0' #define UTIL_ONE '1'
Where available (C++), ditch macros altogether and use a real namespace:
namespace util { const char ZERO = '0'; const char ONE = '1'; };
What is the correct strategy to limit the scope of #define and avoid unwarrented token collisions.
Some simple rules:
#include
guards only. I don't go this far, but it is a good idea to keep preprocessor symbols down to a minimum.
const static
variables rather than named floating point constants.
const UINT ZERO = 0; // Programmer not aware of what's inside Utility.h
First off, if the programmer isn't away of what's inside Utility.h, why did the programmer use that #include
statement? Obviously that UINT
came from somewhere ...
Secondly, the programmer is asking for trouble by naming a variable ZERO
. Leave those all cap names for preprocessor symbols. If you follow the rules, you don't have to know what's inside Utility.h. Simply assume that Utility.h follows the rules. Make that variable's name zero
.
I think you really just have to know what it is you're including. That's like trying to include windows.h and then declare a variable named WM_KEYDOWN. If you have collisions, you should either rename your variable, or (somewhat of a hack), #undef it.
What is the correct strategy to limit the scope of #define and avoid unwarrented token collisions.
Avoid macros unless they are truly necessary. In C++, constant variables and inline functions can usually be used instead. They have the advantage that they are typed, and can be scoped within a namespace, class, or code block. In C, macros are needed more often, but think hard about alternatives before introducing one.
Use a naming convention that makes it clear which symbols are macros, and which are language-level identifiers. It's common to reserve ALL_CAPITALS
names for the exclusive use of macros; if you do that, then macros can only collide with other macros. This also draws the eye towards the parts of the code that are more likely to harbour bugs.
Include a "pseudo-namespace" prefix on each macro name, so that macros from different libraries/modules/whatever, and macros with different purposes, are less likely to collide. So, if you're designing a dodgy library that wants to define a character constant for the digit zero, call it something like DODGY_DIGIT_ZERO
. Just ZERO
could mean many things, and might well clash with a zero-valued constant defined by a different dodgy library.