Understand and Use References in C++
Learn to create and use C++ references so you can alias variables, avoid costly copies, and let functions modify caller data safely.
By the end of this page, you will be able to declare references, pass variables to functions by reference so those functions can modify them, and use const references to share large data without copying it. You will also recognise the most common reference mistakes and know how to avoid them.
What and Why
Every variable you create lives somewhere in memory. When you write int x = 5;, the program sets aside a small region of memory, labels it x, and stores the value 5 there.
A reference is a second name β an alias β for that same region of memory. It does not create a copy of the value; it just gives you another way to reach the exact same bytes.
int x = 5;
int& ref = x; // ref is another name for xAfter that second line, x and ref are the same thing. Changing one changes the other, because they refer to the same memory location.
Why does this matter?
Two situations come up constantly:
-
Avoiding copies. If you have a large object β say, a vector with ten thousand elements β passing it to a function normally makes a full copy of all that data. A reference lets the function work with the original object directly, at almost zero cost.
-
Letting functions modify caller data. Normally, a function cannot change a variable that belongs to the caller. With a reference parameter, it can.
Think of a reference like a nickname. If your name is "Alexander" and your friends call you "Alex", both names refer to the same person. Telling Alex to stand up and telling Alexander to stand up produce the same result.
Step by Step
Declaring a reference
The & symbol after a type name declares a reference type.
#include <iostream>
int main() {
int x = 10;
int& ref = x; // ref is an alias for x
ref = 20; // modifies x through ref
std::cout << x << "\n"; // prints 20
std::cout << ref << "\n"; // prints 20
}Two important rules apply here:
- A reference must be initialised when it is declared. There is no such thing as an empty reference.
- A reference cannot be reseated. Once it is bound to a variable, it stays bound to that variable for its entire lifetime.
References as function parameters
The most common use of references is in function parameters.
#include <iostream>
void addTen(int& n) { // n is a reference to whatever the caller passes
n += 10;
}
int main() {
int score = 42;
addTen(score);
std::cout << score << "\n"; // prints 52
}Without the &, addTen would receive a copy of score, modify that copy, and discard it β the original would stay at 42. The & makes the parameter a reference, so the function modifies the caller's variable directly.
Const references
When you want a function to read a value without copying it and without being able to change it, use a const reference.
#include <iostream>
#include <string>
void greet(const std::string& name) { // read-only alias, no copy
std::cout << "Hello, " << name << "!\n";
// name = "other"; // compile error β const prevents modification
}
int main() {
std::string username = "Ada";
greet(username);
}const std::string& is the idiomatic way to accept any string-like object in a function when you only need to read it. This pattern applies to any type that is expensive to copy.
Common Patterns
Pattern 1 β Swap two values
A classic demonstration of output parameters via reference:
#include <iostream>
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 1, y = 2;
swap(x, y);
std::cout << x << " " << y << "\n"; // prints 2 1
}Both a and b are aliases for the caller's variables, so changes inside swap are immediately visible outside.
Pattern 2 β Range-based for loop with references
When iterating over a collection, using a reference in the loop avoids copying each element. Adding const prevents accidental modification.
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Read each element without copying
for (const int& n : numbers) {
std::cout << n << " ";
}
std::cout << "\n";
// Modify each element in place
for (int& n : numbers) {
n *= 2;
}
for (const int& n : numbers) {
std::cout << n << " "; // prints 2 4 6 8 10
}
std::cout << "\n";
}Pattern 3 β Returning a reference to a member
A class can expose its internals through a reference-returning function, allowing callers to read or write a member directly while the class retains ownership.
#include <iostream>
class Counter {
int value_ = 0;
public:
int& value() { return value_; }
const int& value() const { return value_; }
};
int main() {
Counter c;
c.value() = 7; // write through reference
std::cout << c.value() << "\n"; // prints 7
}This pattern is common in container types (think of std::vector::operator[]).
What Can Go Wrong
Mistake 1 β Dangling reference
A reference must never outlive the variable it refers to. If the original variable is destroyed while the reference still exists, any use of the reference is undefined behaviour β the program may crash, produce garbage, or appear to work correctly until it doesn't.
int& danglingExample() {
int local = 42;
return local; // WRONG: returning reference to a variable
// that is destroyed when the function returns
}Fix: never return a reference to a local variable. Return references only to objects whose lifetime extends beyond the function call β class members, objects passed in by the caller, or objects with static storage.
Mistake 2 β Forgetting const and causing unexpected copies
A non-const reference cannot bind to a temporary value or a literal. This surprises beginners.
void print(int& n) { /* ... */ }
print(5); // compile error: cannot bind non-const reference to a temporaryFix: if the function only reads the value, make the parameter const int&. It will then accept literals, temporaries, and named variables alike.
void print(const int& n) { /* ... */ }
print(5); // fineMistake 3 β Assuming = reseats a reference
Because a reference is an alias, assigning to it modifies the original variable, not the binding.
int a = 1, b = 2;
int& ref = a;
ref = b; // this sets a = 2, not "ref now refers to b"
// a is now 2, ref still refers to aIf you need something that can be rebound, use a pointer instead of a reference.
Quick Reference
| Syntax | Meaning |
|---|---|
int& r = x; | r is an alias for x |
void f(int& n) | n is a reference parameter; function can modify caller's variable |
void f(const int& n) | read-only reference; no copy, no modification |
for (int& e : v) | iterate and modify elements of v in place |
for (const int& e : v) | iterate and read elements of v without copying |
Key rules to memorise:
- A reference must be initialised at the point of declaration.
- A reference cannot be reseated after initialisation.
- Never return a reference to a local variable.
- Prefer
const T&for function parameters when the function only needs to read the value.
What's Next
Now that you understand references, you are ready to explore the concepts they enable:
- Pointers β a lower-level tool that can be reseated and can be null; understanding references first makes pointers easier to grasp.
- Move Semantics β C++11 introduced rvalue references (
T&&), which power efficient resource transfer. - const Correctness β a deeper look at how
constinteracts with references, pointers, and member functions throughout a codebase. - Function Overloading β now that you can write functions with reference parameters, learn how to provide multiple versions of the same function name.