5.3. Comparison with references¶
Recall from our earlier discussions of pass by reference
that the address of
operator &
allows us to pass by reference:
1#include <iostream>
2
3void by_value(int x) {
4 x = 99;
5 std::cout << "in by_value the address of x is "
6 << &x << '\n';
7}
8
9void by_reference (int& x) {
10 std::cout << "in by_ref the address of x is "
11 << &x << '\n';
12 x = -1;
13}
In function by_value
the statement x = 99;
changes the copy provided.
The value of x
is printed, but is destroyed when x
goes out of scope on line 6.
No special character is needed if you want to use a function that takes a reference:
#include <iostream>
int main () {
int beta = 11;
std::cout << "the address of beta is "
<< &beta << '\n';
by_value(beta);
std::cout << "beta = " << beta << '\n';
by_reference(beta);
std::cout << "beta is now "
<< beta << '\n';
}
References do have some definite advantages:
A reference must always be initialized using an existing object. In other words, a reference can never be
null
.A reference can’t be reassigned to a different object
A
const
reference means you can’t modify the thing the reference refers toReferences are simpler, more limited, and inherently safer than pointers
However, there are important things you can’t do with references:
You can’t assign an address to a reference
This would have the effect of having a reference refer to a different object
The technical term for this is that references are not assignable
You can’t operate on a reference
In other words, you can’t increment the referred to memory address, which, by definition, would involve having the reference refer to a different object
You can’t use a single reference to refer to more than one object
You can’t use references in containers such as
vector
Containers can only hold assignable entities
We still need to be able to do all these kinds of memory manipulations. In C++, we achieve these goals using pointers.
5.3.1. Function passing semantics¶
We can pass pointers to a function that expects a reference:
#include <cassert>
void by_reference (int& x) {
x = -1;
}
int main() {
int i = 5;
int* p = &i;
by_reference(p);
assert (i == -1);
return 0;
}
If we pass in only p
, what happens?
The program fails to compile.
We can’t pass an int*
to a function expecting an int&
.
Non-const references vs. pointers
Some programmers consider passing by non-const reference bad style, because the call syntax is the same as pass by value. When a variable is passed into a function by non-const reference there is no visual indication to the programmer of what to expect. Without reading additional documentation or reading the source code, there is no way to know if the function will change its parameter or not.
void func (int& x);
int main() {
int x = 5;
func(x); // will x change?
}
For this reason, a function that takes a non-owning pointer is preferred:
void func (int* x);
int main() {
int x = 5;
func(&x); // Caller expects x to change
}
A function signature is a contract between the function author and the function caller. A function that takes non-const references represents a poorly written contract. Callers don’t know what to expect when the function is called. Even if the parameter isn’t changed today, it might tomorrow. A non-owning pointer makes the intent clear. There is still no requirement to change the parameter, but since the caller is explicitly passing in an address, they can expect it to change.
More to Explore