Language: EN

cpp-macros

What are macros in C++

Macros are a tool that allows us to perform text substitutions before the compilation of the code.

Macros are a feature of the C++ preprocessor (the stage before the code compilation). The preprocessor reads the source code and, when it finds a reference to a macro, replaces the text with the code or expression associated with the macro.

Macros are a feature inherited from the C language

In C++, it is common to abuse the use of macros, either out of habit or the (incorrect) idea that they are the best alternative. But C++, almost always, has better alternatives.

Avoid using them by all means. In fact, don’t use macros 😅

Definition of Macros

Macros are defined using the #define directive followed by the name of the macro and its replacement.

#define MACRO_NAME substitution

These are the simplest macros and consist of substituting an identifier with a constant value or an expression.

#define PI 3.14159
#define MAX_SIZE 100

In this case, PI and MAX_SIZE are object macros that will be replaced by 3.14159 and 100 respectively throughout the code where they are used.

#define PI 3.14159

int main() {
    double radius = 5.0;
    double area = PI * radius * radius;
    return 0;
}

In this example,

  • The macro PI is used to calculate the area of a circle.
  • Before compilation, the preprocessor will replace PI with 3.14159.

It is also possible to define macros that accept parameters, which makes them similar to functions.

But with a very important difference, macros are expanded at compile time (instead of being evaluated at runtime)

#define SQUARE(x) ((x) * (x))

int main() {
    int value = 5;
    int result = SQUARE(value);
    return 0;
}

Here, the macro SQUARE(x) expands to ((value) * (value)), resulting in 25.

Undefining macros

We can remove the definition of a macro using #undef. In fact, it is good practice to remove it to avoid possible conflicts in other parts of the code.

#define MAX_SIZE 100
// ... usage of MAX_SIZE ...
#undef MAX_SIZE

Advantages and disadvantages

If I previously told you to avoid using macros as much as possible, it is logically because they have disadvantages. Let’s talk a bit about this 👇

More or less, all of these are true, but there are also a number of disadvantages.

Disadvantages

  • Difficult-to-debug errors: Because macros simply replace text, they can be a nightmare to debug and fix.

  • Bypass typing: Macros do not respect C++ typing rules.

  • Incompatibility with other parts of the code: Macros cause ALL kinds of problems when mixed with other parts of the code.

  • Multiple evaluations of expressions: In function macros, arguments may be evaluated more than once, which can lead to undesirable behavior.

    #define SQUARE(x) ((x) * (x))
    
    int a = 5;
    int result = SQUARE(a++); // a++ is evaluated twice

In summary, it is a very widespread practice, often defended from the perspective of efficiency (mainly). But we have methods that are practically as efficient, and much cleaner.

Best Practices for Using Macros

If you are going to use Macros (and at some point you will use them, because you don’t listen to me 😆) at least, always keep in mind these practices.

  • Use parentheses in EVERYTHING: Whenever you define a function macro, make sure to use parentheses around the arguments and the entire expression to avoid problems with operator precedence.

    #define SQUARE(x) ((x) * (x))
  • Avoid side effects: Avoid passing expressions with side effects (such as increments ++ or decrements --) to function macros.

  • Prefer constants and functions over macros: In many cases, using constants (const) and inline functions can be a safer and better-typed alternative than macros.

    const double PI = 3.14159;
    
    inline int Square(int x) { return x * x; }