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
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 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 👇
Advantages
- Efficiency: Since macros expand before compilation, they do not introduce runtime overhead.
- 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 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
) andinline
functions can be a safer and better-typed alternative than macros.const double PI = 3.14159; inline int Square(int x) { return x * x; }