Menu
CodeSprout
Typo-tolerant search across every article, problem and post. to open

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++.

kvi5hnu3 min read
0comments
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.

cpp
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.

cpp
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.

cpp
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.

cpp
// 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:

cpp
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 nullptr means "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

kvi5hnu

More in C++

Discussion

Sign in to ask a question or share what you learned.