Programming‎ > ‎C++‎ > ‎Programming Tips‎ > ‎Misc‎ > ‎

Prefer the compiler to the preprocessor

Things to Remember
- For simple constants, prefer const objects or enums to #defines.
- For function-like macros, perefr inline functions to #defines.
 
 
#define MY_MACRO 1.234
The symbolic name MY_MACRO may never be seen by compilers and may not get entered into the symbolic table. The error involving the use of constant can be confusing, because it may refer to 1.234, not MY_MACRO. This problem can also crop in a symbolic debugger. The solution is to replace the macro with a constant.
const double MyMacro - 1.234;
 
As a language constant, MyMacro is definitely seen by compilers and entered into symbol tables. The multiple copies of MY_MACRO are replaced by one copy of MyMacro.
 
The constant pointers should have pointer declared as const, in addition to what the pointer points to.
const char * const myMacro = "Delta";
The string objects are generally preferred to char*.
const std::string myMacro("Delta");
 
The class specific constants should be declared static and member of class, to ensure only one copy.
class MyClass{
    private:
      static const int myCount = 4;    // constant declaration
      static const int myDouble;        // constant declaration with no initialization
      enum {myEnum = 5};               // "the enum hack"
      int myCollection[myCount];        // use of constant
      int myECollection[myEnum];       // fine
      ...
};
 
Usually, C++ requires that you provide a definition for anything you use, but class specific constants that are static and of integral type (e.g. interger, char, bool) are an exception. As long as you don't take their address, you can declare them without providing a definition. A definition might be required in some cases.
      const int MyClass::myCount;        // definition with no value! in implementation file and not header file
The initial value of class constant is provided where the constant is declared, so no value is permitted at the point of definition.
 
When the compiler forbid the in-class initialization at declaration, "the enum hack" is used.
It's legal to take the address of a const, but not of enum and #define. The enum is a good way to enforce the constraint.
The enum hack is a fundamental technique of template metaprogramming.
 
There is no way to create a class-specific constant using a #define, because # define don't respect scope.
 
The in-class initialization is allowed only for integral types and only for constants.
MyClass::myDouble = 1.123;        // const goes in impl file
 
The another (mis)use of the #define directive is using it to implement macros that look like functions but that don't incur the overhead of a function call.
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) :(b))    // call f with max of a and b
You have to remember to parenthesize all the arguments in the macro body. Still, weird things can happen.
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);        // a is incremented twice
CALL_WITH_MAX(++a, b+10);   // a is incremented once
 
The template for an inline function can be used to get all the efficiency of a macro plus all the predictable behavior and type safety of a regular function. It also obeys scope and access rules, being a real function.
template<typename T>                                        // because we don't know
inline void callWithMax(const T& a, const T& b)        // what T is, we pass
{                                                                      // by reference-to-const
    f(a>b ? a : b);
}
Comments