References vs pointers in modern C++
A reference is a safer, cleaner default than a pointer in most places you used to pass a pointer. Here is a decision guide for picking between the two in modern C++.
On this page
C++ gives you two ways to let one function reach another function's variable: pointers (T*) and references (T&). They compile down to the same machine code, but the language's rules around them are very different, and that drives the choice.
What a reference is
A reference is a name for an existing object. It cannot be null, cannot be rebound, and must be bound at the point it is declared.
int x = 10;
int& r = x; // r is another name for x
r = 20; // x is now 20
A pointer is a value that holds an address. It can be null, rebound, pointed into arrays, and arithmetic'd on.
int x = 10, y = 20;
int* p = &x; // p points at x
p = &y; // now p points at y
*p = 30; // y is now 30
p = nullptr; // and now at nothing
Prefer references for parameters
A reference parameter makes two guarantees the compiler enforces: the callee gets a real object, and they cannot swap it for something else.
void grow(std::vector<int>& v) { v.push_back(42); } // mutates
void sum(const std::vector<int>& v) { /* read-only */ } // avoids a copy
Rule of thumb for parameters:
const T&— read-only access to a non-trivial object (strings, vectors, maps).T&— the function will modify it, and the caller should see the change.T— small, trivially-copyable types (int, double, a short struct), or you need your own copy to modify.T*— the parameter is optional (nullable), or it points into a buffer you intend to walk with pointer arithmetic.
If you are reaching for T* for any other reason, a reference is probably clearer.
Returning: never a reference to a local
A reference binds to an existing object. Return a reference to a local and you hand the caller a dangling name.
// WRONG: tmp dies at the end of the function
const std::string& bad() { std::string tmp = "hi"; return tmp; }
// OK: a reference to something that outlives the call
Config& settings() { static Config c; return c; }
Return by value for new objects (RVO and move will usually make the copy free). Return a reference only when the object genuinely outlives the call — a static, a member, something owned by the caller.
Const correctness
A const T& says "I will not change this object." The compiler enforces it, and callers can pass a temporary into it:
void greet(const std::string& name) { /* ... */ }
greet("Vishnu"); // temporary binds to const& fine
A plain T& cannot bind to a temporary — so void grow(std::vector<int>& v) rejects grow(make_vector()). That is a feature: it prevents accidentally mutating something no one will see again.
When pointers are the right call
- The thing is optional — passing
nullptrmeans "nothing to see here." - You are walking a buffer or an array, using pointer arithmetic or iterator semantics.
- You are interoperating with a C API that insists on pointers.
- You need to rebind mid-function — a reference cannot be rebound.
- You are storing it in a container of nullable handles (
std::vector<Widget*>) — references cannot live in containers.
For every other case, a reference says what you mean with fewer footguns.
Prefer references by default; reach for a pointer when you specifically need one of the five things in that list.
Written by
More in C++
Valid Parentheses
Decide whether a string of brackets is balanced. The classic stack warm-up — print "true" or "false" for one input per line.
RAII and smart pointers: never leak again
RAII is the C++ idiom that ties a resource's lifetime to a stack object. Combined with unique_ptr, shared_ptr and weak_ptr, it eliminates whole classes of leaks and dangling pointers.
Discussion
Sign in to ask a question or share what you learned.