References and Const Correctness
Introduction
C gave you pointers — powerful, easy to misuse (NULL, uninitialized, arithmetic errors). C++ adds references: an alias to an existing variable. They look like normal variables but must refer to something valid from birth. Combined with const, you tell the compiler and your teammates what may change — critical in multi-threaded firmware and large codebases.
This article builds on Pointers in depth and prepares you for safe parameter passing in classes.
References — alias, not copy
1 2 3 4 5 | |
| Pointer | Reference | |
|---|---|---|
| Syntax | int *p = &a; |
int &r = a; |
| Rebind to another object? | Yes (p = &b) |
No — bound once |
| Can be "null"? | Yes (nullptr) |
No — must refer to valid object |
| Syntax at use | *p = 5 |
r = 5 |
In plain terms
A pointer is a sticky note with an address you can erase and rewrite. A reference is a nickname you tattoo at birth — "call this variable ref too," forever for that object.
References as function parameters
Avoid copying large objects:
1 2 3 4 5 6 7 | |
Pass read-only data as const T & — efficient and documents intent.
Modify caller data — reference instead of pointer when object must exist:
1 2 3 4 5 6 7 8 | |
Same idea as C pointer version, cleaner call site. Pointers remain for optional arguments (nullptr = not provided) and hardware addresses.
const — promise not to change
1 2 3 4 | |
| Form | Meaning |
|---|---|
const int x |
x cannot change |
const int &r |
alias to something you will not modify through r |
int *const p |
p fixed; *p can change (C rule) |
const int *p |
*p read-only; p can point elsewhere |
Member function const:
1 2 3 4 5 6 7 8 9 10 11 | |
const after ) means "this method does not modify visible object state" — enables calling on const objects and documents thread-safety hints.
References vs pointers in embedded APIs
| Use reference when | Use pointer when |
|---|---|
| Argument always required | Optional (nullptr) |
| Object always valid | C API interop |
| Clean syntax matters | Register address (volatile uint32_t *) |
| Range / output param in C++ style | DMA buffer start |
1 2 3 | |
vs C style void read_adc(int ch, uint16_t *out).
nullptr instead of NULL
C++11 introduces nullptr — typed null pointer constant:
1 2 | |
Avoids ambiguity when NULL is #define 0 and overload resolution breaks. Use in new C++ code; C headers may still use NULL.
Const correctness discipline
- Default to
constuntil you need to mutate. - Pass objects by
const &unless small (int,bool). - Mark methods
constwhen they only read state. mutablerare — allows changing one field insideconstmethod (e.g. cache); use sparingly.
Firmware benefit: compiler catches accidental writes in ISR or shared read paths; code review sees intent in signatures.
Relevant topics
Starting points
- Refactor one
void foo(LargeStruct *p)tovoid foo(const LargeStruct &s). - Add
constto three getter methods — fix compile errors in callers. - Explain why
int &r;without initializer is illegal. - Compare generated assembly for pass-by-value vs pass-by-const-ref (often identical for pointers internally).
Focus points
- References must be initialized — no default "empty reference".
- Do not return reference to local variable — dangling, same as pointer.
const &to temporary extends lifetime of temporary in some cases — know the rule before relying on it.- ISR signatures often stay C pointers for clarity and
volatile.
Key points
- References are non-null aliases; syntax cleaner than pointers for required parameters.
constdocuments read-only intent; const member functions do not modify object state.nullptris the type-safe null pointer in modern C++.- const correctness catches bugs early and clarifies APIs in firmware teams.