Chapter 17

Macros

What macros are, how they work, include guards, function macros, assert, and when to prefer consts, enums, and inlines instead.

In this chapter

  1. What Are Macros?
  2. Include Guards
  3. Function Macros
  4. assert
  5. Macro Pros & Cons
  6. Prefer Consts, Enums, and Inlines
1

What Are Macros?

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
Macros do not check type correctness. The substitution is blind — the preprocessor doesn't know or care about C++ types. A constexpr variable or inline function will always be safer.

2

Include Guards

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.

Most modern compilers support #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.

3

Function Macros

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)
Function macros are resolved before compilation, which provides similar performance to 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++).

4

assert

assert 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();
Use 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.

5

Macro Pros & Cons

Pros
  • Zero runtime overhead — expanded before compilation
  • Can generate code patterns impossible with functions (e.g. stringification)
  • Include guards protect against multiple inclusion
  • Conditional compilation (#ifdef, #if) for platform-specific code
Cons
  • No type safety — the preprocessor is completely type-blind
  • Difficult to debug — macro expansions don't appear in error messages clearly
  • No scoping — macros pollute the global namespace and can't be private
  • No debugger visibility — symbols defined by macros often aren't loaded

6

Prefer Consts, Enums, and Inlines to #define

For the vast majority of macro use cases, C++ has better alternatives:

Instead of…Use…Why
#define PI 3.14constexpr double PI = 3.14;Type-safe, debugger-visible, can be scoped
#define SQUARE(x) ((x)*(x))inline / constexpr functionType-safe, proper scoping, handles side effects correctly
#define MAX_SIZE 5 as class constantEnum hack or static constexprKeeps the constant inside the class, no external linkage issues

The Enum Hack

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
};
Unlike a 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.
← Chapter 16 ↑ Index Chapter 18 →