Lesson 3 of 6
A reference is an alias: a second name for an object that already exists.
#include <iostream>
int main() {
int x = 42;
int& r = x; // r IS x, under another name
std::cout << x << " " << r << "\n";
r = 99; // writes x
std::cout << x << " " << r << "\n";
x = 7;
std::cout << x << " " << r << "\n";
return 0;
}
42 42
99 99
7 7
There is one int in that program. x and r never disagree, because they are the same object.
You met references already in lesson 5.3, as the way to let a function modify its caller's variable. Now the surrounding machinery makes sense: a reference is, in practice, a pointer the compiler dereferences for you automatically.
The three differences from a pointer
1. A reference must be initialised, and cannot be null.
int x = 1;
int& r = x; // fine
int& bad; // error: declared as reference but not initialized
There is no such thing as a reference that refers to nothing. That is the main safety benefit:
code taking int& need not check for null, because there is nothing to check.
2. A reference can never be re-seated.
Once bound, a reference refers to that object for its whole life:
#include <iostream>
int main() {
int a = 1;
int b = 2;
int& r = a;
r = b; // this does NOT make r refer to b — it assigns b's value to a
std::cout << a << " " << b << " " << r << "\n";
return 0;
}
2 2 2
Read that carefully. a became 2. r = b wrote through the alias, exactly as a = b would.
Compare with a pointer, where p = &b genuinely re-points.
This is the one place references surprise people. There is no syntax for re-seating a reference, so if you need to change what you are referring to, you need a pointer.
3. A reference needs no dereference operator.
*p versus plain r. Cleaner to read, and one fewer way to make a mistake.
Side by side
#include <iostream>
void by_pointer(int* p) {
if (!p) return; // must check
*p += 1; // must dereference
}
void by_reference(int& r) {
r += 1; // that's all
}
int main() {
int x = 10;
by_pointer(&x); // caller must take the address
std::cout << x << "\n";
by_reference(x); // caller writes nothing special
std::cout << x << "\n";
return 0;
}
11
12
Note the call sites. by_pointer(&x) announces at the call that something may be modified.
by_reference(x) looks exactly like a by-value call — you cannot tell from the call alone that
x might change. That is the trade: references are cleaner to write and less obvious to read.
const references, and why they are everywhere
A const reference gives access without permission to modify:
#include <iostream>
#include <string>
void print_it(const std::string& s) {
std::cout << s << " (" << s.size() << " chars)\n";
// s = "no"; would not compile: s is const
}
int main() {
std::string name = "Sokha";
print_it(name);
print_it("a temporary"); // works: const& binds to temporaries
return 0;
}
Sokha (5 chars)
a temporary (11 chars)
Two things make const T& the default for parameters that are not small:
- No copy. Passing a large
std::stringorstd::vectorby value copies every byte. - It binds to temporaries.
print_it("a temporary")creates a temporarystd::string, and aconst&can bind to it. A plainint&orstd::string&cannot bind to a temporary, which is the compiler stopping you from modifying something that is about to disappear.
When to use which
Use a reference by default. Use a pointer when you need one of the things a reference cannot do:
- It might be absent. A pointer can be
nullptr; a reference cannot express "no object". - It needs to change target. Walking through an array, re-pointing at a different object.
- You need arithmetic on it (lesson 7.4).
Modern C++ leans heavily on references, and on smart pointers (Module 9) when ownership is involved. Raw pointers are for non-owning, possibly-absent views.
Try it
Write two functions that both double an int — one taking int*, one taking int&. Then try
to write a function that takes int& and makes it refer to a different variable. Explain what
happens.
Checkpoint
- A reference is another name for an existing object, not a copy of it.
- It must be initialised, can never be null, and can never be re-seated.
r = otherassigns through the reference; it does not rebind it.- No
*needed — a reference is used exactly like the object it names. const T&is the default for non-trivial parameters: no copy, and it binds to temporaries.- Reach for a pointer only when you need absence, re-targeting, or arithmetic.
Questions about this lesson
Log in to ask a question.