DIMAJIX - software consulting (http://www.dimajix.de/)
 

Preprocessor

The preprocessor of a C++ compiler isn't very powerful and many things that very done using macros in C can be done much better using templates in C++. So there are only very few uses left for the preprocessor. This section tries to propose better solutions for some obsolete constructs.


Rule 16. Use #define's only to prevent multiple inclusions and for target machine specific compilation.

This probably is the golden rule concerning the C++ preprocessor, the two exceptions mentioned in this rule still cannot be handled in a different way and it is absolutely okay and good style to use the preprocessor for this type of problems.

In order to point out some of the many inherent problems of the preprocessor in other cases, have a look at the following piece of Code:

#define print(x)	    printf("%s\n",x);

...

class A {
public:
    void    print(const char* );
};

Of course this code will not compile, simply because the method print in class A will be substituted due the macro definition. Probably not what the programmer intended to do. Another problem with macros are side-effects. Consider the following piece of code:

#define max(a,b)    (((a) > (b)) ? (a) : (b))

...

m = max(i++,j);

Not only is the definition of the macro max ugly due to the many parentheses (which are really needed!), but in the assignment of the variable m it is even not clear if the increment operation i++ is called once or twice.

One of the basic problems behind the negative side effects of #define's is the simple fact that #define's do not obey any scoping, that means you cannot #define a macro within a single scope or namespace - it is always globally visible. The following rule offers a simple and robust solution to many problems:

Rule 17. Use inline functions and/or templates instead of #define'ed macro functions.

The examples above can be rewritten using inline functions and/or templates as follows:

// Better solution than a print-macro
inline void print(const char* str) {
    printf("%s\n",str);
}

// Better solution than a max-macro
inline int max(int a, int b) {
    return (a > b ? a : b); 
}

In the case of the max-macro this solution may still not be satisfying, because the inline function is only defined for int's but not for float's, double's or other data types. One solution would be to create different overloads, but the more elegant and even type-safe solution is to use templates. Then the code will look like this:

template<class T>
inline const T& max(const T& a, const T& b) {
    return (a > b ? a : b);
}

Also note the usage of the references (&) which can boost performance and avoid copy operations if T is an abstract data type (for example a fractional with arbitrary precision).


Rule 18. Use const int or enum instead of #define'ed constants.

In order to work around #define's for constant values there are basically two methods available in C++: The first method declares a const int with an immediate definition. The second method simply uses an enum, although this is considered as a hack in certain circumstances. Consider the following examples.

// Proper use of const int as global constant
const int MaxItems = 100;

// Alterantive definition using an enum
enum {
    MaxItems = 100;
};

// Both techniques inside a class definition
class List {
public:
    static const int MaxItems = 100;
    enum Size {
        Big = 1,
        Medium = 2,
        Small = 3
    }; 
    
    ...
};

Note that not all compilers understand the usage of const int with an immediate definition correctly, so it is best to use enum's instead.

Kaya Memisoglu 2005-01-06