Lesson 3 of 8
Lesson 5.2 ended on a frustration: swap_values couldn't swap anything, because it only ever saw
copies. References fix that.
A reference is another name for the same object
#include <iostream>
int main() {
int original = 10;
int& alias = original; // alias IS original, under another name
alias = 20;
std::cout << original << " " << alias << "\n";
original = 30;
std::cout << original << " " << alias << "\n";
return 0;
}
20 20
30 30
There is only one int here. alias isn't a copy and isn't a pointer — it's a second label on
the same box. Change either name and you've changed the one object.
Two rules follow:
- A reference must be initialized when declared. There's no such thing as a reference to nothing.
- It can never be reseated.
alias = 20;assigns through the reference; it doesn't makealiasrefer to something else. A reference is bound for life.
Passing by reference
Put & in the parameter list and the function gets the caller's actual object:
#include <iostream>
void set_to_ninety_nine(int& value) {
value = 99;
}
int main() {
int number = 5;
set_to_ninety_nine(number);
std::cout << number << "\n";
return 0;
}
99
Compare with lesson 5.2's version, which printed 5. The only difference is one &.
Now the swap works:
#include <iostream>
void swap_values(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 1;
int y = 2;
swap_values(x, y);
std::cout << "x=" << x << " y=" << y << "\n";
return 0;
}
x=2 y=1
The call site looks identical
This is the thing to be careful about:
modify(number); // by reference — number may change
inspect(number); // by value — number cannot change
Nothing at the call site tells you which. Unlike some languages, C++ has no ref keyword at the
point of call — you have to look at the function's declaration.
That's a real readability cost, and it's the argument for the discipline in lesson 5.4: use
const& when you're only reading, so that a plain & becomes a reliable signal that the
function will modify the argument.
Two reasons to pass by reference
1. To modify the caller's object. The examples above.
2. To avoid copying. Often the more important one:
#include <iostream>
#include <string>
void by_value(std::string text) {
text += "!"; // modifies the copy
}
void by_reference(std::string& text) {
text += "!"; // modifies the original
}
int main() {
std::string a = "hello";
std::string b = "hello";
by_value(a);
by_reference(b);
std::cout << a << "\n";
std::cout << b << "\n";
return 0;
}
hello
hello!
by_value copied the whole string, appended to the copy, and threw it away.
References can't bind to temporaries
A non-const reference requires an actual object with a name:
// WILL NOT COMPILE
void modify(int& value) {
value = 10;
}
int main() {
modify(5); // 5 is a temporary — nothing to modify
return 0;
}
error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
This makes sense: the function promises to write to its argument, and 5 isn't somewhere you can
write. A const& can bind to a temporary, which is one more reason it's the better default.
Returning a reference
Functions can return references too — usually to something the caller already owns:
#include <iostream>
int& largest(int& a, int& b) {
return (a > b) ? a : b;
}
int main() {
int x = 3;
int y = 8;
largest(x, y) = 100; // assigns to whichever was larger
std::cout << x << " " << y << "\n";
return 0;
}
3 100
largest(x, y) = 100; looks strange but follows directly: the call is y, so assigning to it
assigns to y. This is how vector's operator[] lets you write v[0] = 5;.
The rule from lesson 5.2 still stands absolutely: never return a reference to a local. Here
a and b are references to the caller's objects, which outlive the call.
Choosing
| Pass as | When |
| --- | --- |
| T (value) | small and cheap (int, char, double, bool), and you don't need to modify the caller's |
| const T& | you only read, and copying would be wasteful — the default for strings and objects |
| T& | you genuinely need to modify the caller's object |
Try it
Write void add_and_report(int& total, int amount) that adds amount to total and prints the
new value. Note that one parameter is a reference and one isn't — and be able to say why.
Checkpoint
- A reference (
T&) is another name for an existing object — no copy, no pointer. - References must be initialized and can never be rebound.
- A reference parameter lets the function modify the caller's object.
- The call site looks the same either way; you must read the declaration.
- Non-
constreferences can't bind to temporaries like5. - Never return a reference to a local variable.
Questions about this lesson
Log in to ask a question.