Language: EN

cpp-macros

What are macros in C++

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

Macros are a feature of the C++ preprocessor (the stage before 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 (wrong) idea that they are the most efficient alternative. However, in C++, we almost always have better alternatives.

Avoid using them by all means. Moreover, do not use macros 😅

Definition of Macros

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

#define MACRO_NAME substitution

They 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 potential conflicts in other parts of the code.

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

Advantages and disadvantages

If I have 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 evaluation of expressions: In function macros, arguments may be evaluated more than once, leading to undesired behaviors.

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

In summary, while it is a widely practiced approach, often defended from the perspective of efficiency (mainly). But we have methods that are practically just as efficient, and much cleaner.

Best practices when using macros

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

  • Use parentheses in EVERYTHING: Whenever you define a function macro, make sure to use parentheses around both the arguments and the entire expression to avoid issues 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 to macros.

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