What macros are, how they work, include guards, function macros, assert, and when to prefer consts, enums, and inlines instead.
Macros are preprocessor directives — they are processed before compilation begins. The preprocessor performs a direct text substitution everywhere the macro name appears:
#define ARRAY_LENGTH 25
#define SQUARE(x) ((x) * (x))
Everywhere the preprocessor sees ARRAY_LENGTH, it replaces it with 25. Everywhere it sees SQUARE(4), it replaces it with ((4) * (4)). This happens before the compiler ever sees the code.
// Macro used like a typedef
#define MY_DOUBLE double
// What type is this?
#define PI 3.1416
// Answer: no type — it's just the text "3.1416"
// The preprocessor pastes it in; the compiler decides what to do with it
constexpr variable or inline function will always be safer.A header file included multiple times would cause duplicate declarations. Include guards use macros to prevent this:
#ifndef MYHEADER_H // if MYHEADER_H has not been defined yet...
#define MYHEADER_H // define it now
// ... all your declarations ...
#endif // end of the guard
On the first #include, MYHEADER_H is undefined so the block runs. On subsequent includes, MYHEADER_H is already defined so the entire block is skipped.
#pragma once as a simpler alternative — place it at the top of the header and the compiler handles the guard automatically. It is not technically standard C++ but is supported everywhere in practice.Macros can take parameters and behave like functions. They are expanded inline — similar to inline functions — with no call overhead:
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
The excessive parentheses are required to ensure correct order of operations when the macro arguments are expressions:
// Without parens around x:
// SQUARE(2+3) → 2+3 * 2+3 → 2 + 6 + 3 → 11 (wrong!)
// With parens around x:
// SQUARE(2+3) → ((2+3) * (2+3)) → (5 * 5) → 25 (correct)
inline functions. But unlike inline functions, they are not type-safe and can produce subtle bugs — especially with expressions that have side effects like SQUARE(i++).assertassert is technically a macro defined in <cassert>. It evaluates an expression and halts the program if it is false — but only in Debug builds. It is compiled away entirely in Release builds (NDEBUG defined):
#include <cassert>
int* ptr = GetPointer();
assert(ptr != nullptr); // stops execution in debug if ptr is null
ptr->DoSomething();
assert to document and enforce invariants that should never be false if your code is correct. Do not use it for runtime error handling (user input, file not found, etc.) — those require real error handling that works in Release builds too.#ifdef, #if) for platform-specific codeprivate#defineFor the vast majority of macro use cases, C++ has better alternatives:
| Instead of… | Use… | Why |
|---|---|---|
#define PI 3.14 | constexpr double PI = 3.14; | Type-safe, debugger-visible, can be scoped |
#define SQUARE(x) ((x)*(x)) | inline / constexpr function | Type-safe, proper scoping, handles side effects correctly |
#define MAX_SIZE 5 as class constant | Enum hack or static constexpr | Keeps the constant inside the class, no external linkage issues |
When you need a compile-time integer constant inside a class and can't use static constexpr (older compilers), an anonymous enum works:
class GameBoard {
private:
enum { NumTurns = 5 }; // compile-time constant, class-scoped
int scores[NumTurns]; // valid — NumTurns is a compile-time value
};
static const int member, the enum hack guarantees no storage is allocated for the constant and outside code cannot take its address — making it strictly more encapsulated.