What Is a Reference in C++? Reference vs Pointer Explained
A C++ reference is an alias for an existing object, bound once and never null. How it differs from a pointer, const references, and dangling references.
- Course: C++ study plan
- Module: Arrays, pointers and references
- Kind: Lesson
- Reading time: 14 min
- Runtime: Clang 18 · C++20
What is a reference in C++?
A reference in C++ is an alias: another name for an existing object. After int& r = x;, reading or assigning r reads or assigns x, and &r == &x. A reference must be initialised when declared, cannot be reseated to refer to a different object, and cannot be null. const T& parameters pass large objects without copying them.
Lesson
A reference is another name for an existing object. Write int& r = x; and from then on r is x: reading r reads x, assigning to r assigns to x, and &r == &x. The rest of the lesson is the rulebook that follows — must be initialised, cannot be reseated, cannot be null — the two places references matter most, parameters and return values, the failure mode every C++ programmer meets, the dangling reference, and the table that decides between a reference and a pointer.
An alias, not an object
int x = 5;
int& r = x; // r is an alias for x
r = 7; // x is now 7
++r; // x is now 8
std::cout << x << ' ' << r << '\n'; // 8 8
int y = 100;
r = y; // NOT a rebind: copies y's value into x — x is 100, r still names x
std::cout << x << ' ' << (&r == &x) << '\n'; // 100 1
The line r = y; is the one that separates references from pointers. It does not make r refer to y; there is no syntax that does. Once bound, a reference names the same object for its whole life. The compiler may implement it as a hidden pointer, but the language treats it as the object itself: no *, no ->, no arithmetic.
The rules, all consequences of "it is the object":
- Must be initialised.
int& r;is a compile error — a name for nothing is meaningless. - Cannot be reseated. Assignment goes through to the referent.
- Cannot be null. A legitimately obtained reference always refers to an object;
int& r = *p;with a nullpis undefined behaviour, not a null reference. - No references to references, arrays of references or pointers to references.
&ris the address of the referent,&x.
Reference parameters
void swap_values(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
int x = 1, y = 2;
swap_values(x, y); // x is 2, y is 1 — no & at the call site
A T& parameter lets the callee modify the caller's object. Compared with the pointer version in lesson 2 there are no stars in the body and no ampersands at the call site — cleaner, and less visible, so the function's name has to say that its arguments change (swap_values, sort_in_place, read_into).
The read-only form is the workhorse of the language: long long total(const std::vector<int>& values) reads the vector without copying it and cannot modify it. Module 4 (Functions) gave the rule: by value when the type is small (int, double, a pointer) or you need your own copy; by const T& for anything larger that you only read; by T& when the function's job is to change it. const T& on a std::vector of a million elements costs eight bytes; by value costs a million copies.
const T& binds to temporaries
const int& r = 5; // OK: a temporary int holds 5; r refers to it
const std::string& s = "hello"; // OK: a temporary std::string is built from the literal
int& bad = 5; // error: a non-const lvalue reference cannot bind to a temporary
A non-const T& must bind to an lvalue — a named object. A const T& may also bind to a temporary, and when the reference is a local the temporary lives as long as it does (lifetime extension). This is what lets void greet(const std::string& name) be called as greet("Ada"): a std::string is made for the call and destroyed after it. It is also why void f(int& n) cannot be called as f(5) — there is no n in the caller to modify. Module 9 (Copies, moves and the rule of five) introduces the third kind, T&&, which binds only to temporaries.
Returning a reference
int& at_index(std::vector<int>& v, std::size_t i) { return v[i]; }
std::vector<int> v{1, 2, 3};
at_index(v, 1) = 20; // the call is an lvalue: assign through it — v is {1, 20, 3}
A function that returns T& hands back an alias to something that already exists — an element of a container the caller passed, a member of *this (Module 8), a static. std::vector::operator[] and std::map::operator[] return references; that is why v[i] = x works. The one rule: the object must outlive the call.
Dangling references
int& broken() {
int local = 3;
return local; // warning: reference to local variable 'local' returned
}
int& r = broken(); // r names an object that no longer exists: undefined behaviour to read
local is destroyed when broken returns; the reference outlives it and reads memory that now belongs to whichever function is called next. GCC and Clang warn — treat the warning as an error. A by-value parameter is a local too, so returning a reference to one is the same bug.
The second classic is subtler, because nothing is returned:
std::vector<int> v{1, 2, 3};
int& first = v[0];
v.push_back(4); // may reallocate: the elements move to a new block, the old one is freed
first = 9; // undefined behaviour if it did — first refers to freed memory
A reference (or pointer, or iterator) into a std::vector is valid only until the next operation that may move the elements: push_back, insert, resize, reserve. This is invalidation, and Module 13 lesson 6 gives the table per container. The habit: use a reference into a container before the container changes, or hold an index, which survives.
References in range-for
std::vector<int> v{1, 2, 3};
for (int x : v) x *= 2; // x is a copy; v is unchanged
for (int& x : v) x *= 2; // x aliases each element; v is {2, 4, 6}
for (const auto& x : v) std::cout << x; // read-only, no copy — the default for anything big
The same rule extends to auto: auto x = v[0]; copies, auto& x = v[0]; aliases.
References versus pointers
Reference T& | Pointer T* | |
|---|---|---|
| Can be null | no | yes (nullptr) |
| Must be initialised | yes | should be; the language does not force it |
| Can be reseated | no | yes |
| Syntax at use | as the object itself | *p, p->m |
| Arithmetic | none | p + i, q - p |
| Mutation visible at the call site | no (f(x)) | yes (f(&x)) |
| Use it for | parameters, aliases, operator[] returns | optional values, cursors, arrays, owning dynamic memory |
Default to a reference; reach for a pointer when the object may be absent, must be reseated, or is an array or dynamic memory.
Pitfalls
autocopies.auto x = v[0]; x = 5;changes a copy. Writeauto&.- Returning a reference to a local, or to a parameter passed by value (also a local).
- Holding a reference across
push_back. Re-read it afterwards, or use an index. conston the reference, not the object.const int& r = x; x = 9;—rsees 9; theconstonly forbids writing throughr.
Key takeaways
- A reference is an alias: bound once at initialisation, never reseated, never null.
- Parameters:
Tfor small or owned,const T&for read-only,T&for in-out; name the function so the mutation is visible. const T&binds to temporaries (lifetime-extended when local);T&binds only to lvalues.- Return a reference only to something that outlives the call — never a local, and remember a
std::vectorcan move its elements. - Reference by default; pointer when absence, reseating or arrays are involved.
Common questions
What is the difference between a reference and a pointer in C++?
A reference is an alias that must be initialised, cannot be null and cannot be reseated, and it is used as if it were the object. A pointer holds an address, can be nullptr, can be reassigned, needs * or -> to reach the object and supports arithmetic. Default to references; use pointers for optional objects, cursors, arrays and dynamic memory.
Can a reference be reassigned in C++?
No. Once bound, a reference names the same object for its whole life. r = y; does not rebind r to y; it copies y's value into the object r already refers to. There is no syntax that reseats a reference.
Why can a const reference bind to a temporary?
A const T& may bind to a temporary because it promises not to modify it, and when the reference is a local the temporary's lifetime is extended to match. That lets greet(const std::string& name) be called as greet("Ada"). A non-const T& binds only to lvalues, so f(5) fails for void f(int&).
What is a dangling reference in C++?
A reference to an object that no longer exists. The classic cases are returning a reference to a local variable, which is destroyed when the function returns, and holding a reference into a std::vector across push_back, which may move the elements. Reading through it is undefined behaviour.
Does auto make a copy or a reference in C++?
auto deduces a value type, so auto x = v[0]; makes a copy and changes to x do not reach v. Write auto& x = v[0]; to alias the element, or const auto& to read without copying — the same rule as for (auto& x : v) in a range-for loop.
Exercises
Sort three by reference
Read three integers. Write void swap_values(int& a, int& b) and void sort3(int& a, int& b, int& c, int& swaps) that puts the three into ascending order using only swap_values on pairs — compare a,b, then b,c, then a,b again — and counts every swap performed through the swaps in-out reference. Print the sorted values and the count.
Input: one line with three integers. Output: the three integers in ascending order on one line, then swaps=<count>.
Example: input 3 1 2 →
1 2 3
swaps=2Edit through a returned reference
Read n (1 ≤ n ≤ 100) integers into a std::vector<int>, then an index i (0 ≤ i < n) and a value x. Write int& at_index(std::vector<int>& values, std::size_t i) returning a reference to element i, and assign x through the call: at_index(values, i) = x;. Then write void stats(const std::vector<int>& values, long long& sum, int& largest) that fills its two out-parameters, and print the results.
Input: n, then n integers, then i x. Output: the vector after the assignment on one line, then sum=<sum> largest=<largest>.
Example: input 4 1 2 3 4 2 10 →
1 2 10 4
sum=17 largest=10In this module: Arrays, pointers and references
- Built-in arrays and std::array
- Pointers — addresses, nullptr and the arrow
- Pointer arithmetic and arrays
- References — an alias with a rulebook (this lesson)
- Const correctness — reading declarations right to left
- Multidimensional data — grids, layout and neighbours
- Checkpoint — Arrays, pointers and references
← Pointer arithmetic and arrays · Const correctness — reading declarations right to left →