Recall that operations on a reference are actually operations on the object to which the reference refers (§ 2.3.1, p. 50):
int n = 0, i = 42;
int &r = n; // r is bound to n (i.e., r is another name for n)
r = 42; // n is now 42
r = i; // n now has the same value as i
i = r; // i has the same value as n
Reference parameters exploit this behavior. They are often used to allow a function to change the value of one or more of its arguments.
As one example, we can rewrite our reset
program from the previous section to take a reference instead of a pointer:
// function that takes a reference to an int and sets the given object to zero
void reset(int &i) // i is just another name for the object passed to reset
{
i = 0; // changes the value of the object to which i refers
}
As with any other reference, a reference parameter is bound directly to the object from which it is initialized. When we call this version of reset
, i
will be bound to whatever int
object we pass. As with any reference, changes made to i
are made to the object to which i
refers. In this case, that object is the argument to reset
.
When we call this version of reset
, we pass an object directly; there is no need to pass its address:
int j = 42;
reset(j); // j is passed by reference; the value in j is changed
cout << "j = " << j << endl; // prints j = 0
In this call, the parameter i
is just another name for j
. Any use of i
inside reset
is a use of j
.
It can be inefficient to copy objects of large class types or large containers. Moreover, some class types (including the IO types) cannot be copied. Functions must use reference parameters to operate on objects of a type that cannot be copied.
As an example, we’ll write a function to compare the length of two string
s. Because string
s can be long, we’d like to avoid copying them, so we’ll make our parameters references. Because comparing two string
s does not involve changing the string
s, we’ll make the parameters references to const
(§ 2.4.1, p. 61):
// compare the length of two strings
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
As we’ll see in § 6.2.3 (p. 213), functions should use references to const
for reference parameters they do not need to change.
Reference parameters that are not changed inside a function should be references to const
.
A function can return only a single value. However, sometimes a function has more than one value to return. Reference parameters let us effectively return multiple results. As an example, we’ll define a function named find_char
that will return the position of the first occurrence of a given character in a string
. We’d also like the function to return a count of how many times that character occurs.
How can we define a function that returns a position and an occurrence count? We could define a new type that contains the position and the count. An easier solution is to pass an additional reference argument to hold the occurrence count:
// returns the index of the first occurrence of c in s
// the reference parameter occurs counts how often c occurs
string::size_type find_char(const string &s, char c,
string::size_type &occurs)
{
auto ret = s.size(); // position of the first occurrence, if any
occurs = 0; // set the occurrence count parameter
for (decltype(ret) i = 0; i != s.size(); ++i) {
if (s[i] == c) {
if (ret == s.size())
ret = i; // remember the first occurrence of c
++occurs; // increment the occurrence count
}
}
return ret; // count is returned implicitly in occurs
}
When we call find_char
, we have to pass three arguments: a string
in which to look, the character to look for, and a size_type
(§ 3.2.2, p. 88) object to hold the occurrence count. Assuming s
is a string
, and ctr
is a size_type
object, we can call find_char
as follows:
auto index = find_char(s, 'o', ctr);
After the call, the value of ctr
will be the number of times o
occurs, and index
will refer to the first occurrence if there is one. Otherwise, index
will be equal to s.size()
and ctr
will be zero.
Exercise 6.11: Write and test your own version of reset
that takes a reference.
Exercise 6.12: Rewrite the program from exercise 6.10 in § 6.2.1 (p. 210) to use references instead of pointers to swap the value of two int
s. Which version do you think would be easier to use and why?
Exercise 6.13: Assuming T
is the name of a type, explain the difference between a function declared as void f(T)
and void f(T&)
.
Exercise 6.14: Give an example of when a parameter should be a reference type. Give an example of when a parameter should not be a reference.
Exercise 6.15: Explain the rationale for the type of each of find_char
’s parameters In particular, why is s
a reference to const
but occurs
is a plain reference? Why are these parameters references, but the char
parameter c
is not? What would happen if we made s
a plain reference? What if we made occurs
a reference to const
?