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
with3.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 👇
Advantages
- Efficiency: Since macros are expanded before compilation, they do not introduce overhead at runtime.
- Reusable and portable code: Macros allow encapsulating repetitive code patterns, or allow moving it to another system or device.
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
) andinline
functions can be a safer and better-typed alternative to macros.const double PI = 3.14159; inline int Square(int x) { return x * x; }