Pass by Reference

Today introduces a new paradigm in how you call functions. Up until this point, everything in the class has been pass-by-value. The value of an argument to a function is copied into the function's parameter. Today introduces a way to not copy, but instead directly change the value in the calling function's variable.

New Idea: Pass by Reference rather than Value

Recall that arguments to our functions have been passed by value, meaning that inside the function we get a copy of the argument object given in the function call. Sometimes, however, we'd like to get the actual object from the function call rather than a copy. There are 3 basic reasons for this:

  1. We may want to modify the object.
  2. A "copy" may not make sense for some objects.
  3. If an object is "large", copying may be expensive in terms of time or memory space.

How passing by reference works

Let's take a simple example to see how passing by reference works. Suppose we have a variable that stands for the hour (with a 24 hour clock), and we go through a simulation that keeps incrementing the hour. A simple ++ won't do, because once we hit 25, the hour goes back to 1. So, we might want a function incHour that increments the hour correctly ... which means that the hour variable with which the function gets called should be modified. We might be tempted to say something like this:


void incHour(int);
...
void incHour(int h) {
  h++;
  if (h == 25)
    h = 1;
}
However, since pass-by-value gives us a copy, nothing really gets changed! (See diagram on right.)
We need to pass the hour by reference, which means we get the actual object from the function call, not a copy. This is indicated in C++ by putting an & after the type of the argument.


void incHour(int&);
...
void incHour(int& h) {
  h++;
  if (h == 25)
    h = 1;
}

The visualization here is that the parameter h is like a wormhole to the argument incHour was called with. (See diagram on right.) Now this does what we want.

For example, if this function definition is used in the following code fragment:

int hour = 18;
for(int i = 0; i < 8; i++) {
  cout << "Hour " << i + 1 
       << " of my 8 hour day starts at "
       << hour << ":00" << endl;
  incHour(hour);
}

Important Note: This example is good for showing how pass-by-reference works, but a lousy example for showing when you should be using it! In this case, you should write incHour() as a function that takes its parameter with pass-by-value and returns the new time, rather than modifying its argument.

The above code fragment will print out something like this:
Hour 1 of my 8 hour day starts at 18:00
Hour 2 of my 8 hour day starts at 19:00
Hour 3 of my 8 hour day starts at 20:00
Hour 4 of my 8 hour day starts at 21:00
Hour 5 of my 8 hour day starts at 22:00
Hour 6 of my 8 hour day starts at 23:00
Hour 7 of my 8 hour day starts at 24:00
Hour 8 of my 8 hour day starts at 1:00

No constant values for passing by reference.
Note that incHour(6) will not compile! Why? Well when you pass something like this by reference, that says that it might get modified, and the constant value 6 is not something that can be modified! You need to pass variables, not constants, to functions that take arguments by reference rather than by value.

Terminology: lvalue vs. rvalue
If a function takes a parameter by reference, the technical term for what kind of object can be an argument is lvalue, which literally means "anything that can appear on the left-hand side of an assignment". Another term involved in these kinds of discussions is rvalue, which means anything that can appear on the right-hand side of an assignment — which is a much bigger class of objects. We require lvalues for the left-hand sides of assignments (obviously), for arguments to functions in which the parameter is passed by reference, and also as operands for ++ and -- expressions (and +=, -=. *=, ...).

Reason 1: When We Want to Modify the Object

The Famous swap!

With multi-parameter functions, we really start to see some interesting reasons to use pass-by-reference. For example, one of the most common operations in computing is the swap.

For example, suppose we read two int's in from the user and we want to print out all the integer values from the smaller of the two up to the larger (comma-separate). If the user is kind enough to enter the numbers so that the frst is the smaller, we would write:


int a, b;
cin >> a >> b;
for( int i = a; i < b; i++ )
  cout << i << ',';
cout << b << endl;
But what if we want to allow the user to enter the two numbers in either order, and have the program work regardless? If we had a function swap that took two ints and swapped their values, we could do the following:

int a, b;
cin >> a >> b;
if( b < a )
  swap(a,b);                // the famous swap!
for( int i = a; i < b; i++ )
  cout << i << ',';
cout << b << endl;
... which is a whole lot more convenient than, for example, writing separate for-loops for the a<b case and the b<a case.

The function swap will need to change the values of the variables it's passed, so they must be passed by reference.


void swap(int&, int&);
...
void swap(int& a, int& b) {
  int temp = a;
  a = b;
  b = temp;
}

Using reference to return multiple values

Sometimes there are several things we'd like to return with a function. For example, if you have a program that works with vectors, you might want to convert back and forth from the (r,theta) and (x,y) representation. It'd be nice to have a function that would do this, so I'd give it (r,theta) and it'd give me the appropriate (x,y). Unfortunately, there are two values we'd need to return from our function, and a function always returns at most one thing. What we can do, is pass a variable for x and a variable for y by reference, and modify those variables inside the function so that they contain the proper values.

void polar2rect(double,double,double&,double&);
...
void polar2rect(double r, double theta, double &x, double &y) {
  x = r*cos(theta);
  y = r*sin(theta);
}
With this definition, if I had a vector represented by doubles radius and angle, I could convert it to rectangular coordinates stored in variables x and y by writing:
polar2rect(radius,angle,x,y);

Reason 2: When copying an object doesn't make sense

Reason 2 from my list of reasons to pass by value is that copying doesn't make sense for some type of objects.

Passing istream by reference

Consider the type istream.

Recall: Both cin and all the ifstream objects we declare are of type istream.

Suppose:

It would be wonderful to have a function readtime that could take either cin or filein as an argument, read the ellapsed time in hh:mm:ss format, and return it in seconds. Since both are istream objects, it would be natural to write the function

int readtime(istream);

Problem: It doesn't make sense to copy an input stream object!

What would it mean? For example:

Copying doesn't make sense. So, we must pass stream objects (both ostream and istream) by reference.

With this in mind, we'd define:


int readtime(istream&);
...
int readtime(istream& in) {
	int h, m, s;
	char c;
	in >> h >> c >> m >> c >> s;
	return h*3600 + m*60 + s;
}

With these definitions, I can say


int k = readtime(cin);
when I need to read in a time from the keyboard, and I can say:

int m = readtime(filein);
... when I need to read in a time from the file to which filein is attached.

Special Note: Why pass filestreams?

Why passing filenames to functions usually is a bad idea

Suppose I want to write a program that reads in two lengths from a file, given in x' y" format, and print out the difference in inches. It should work something like this:

Program Run data.txt
Enter file name: data.txt
3" difference
3' 2"
2' 11"

As I ponder how to do this, I realize code that reads a length and converts it to inches consists of a couple of lines, and those lines are the same whether I'm reading the first length or the second. So, in top-down design fashion I say to myself "It sure would be nice to have a function readLength that would take care of the reading and converting for me." And so we might arrive at the following proposed solution.

Prototype and definition Main

int readLength(string fname);

int readLength(string fname) {
	ifstream fin(fname.c_str());
	int f, i;
	char c;
	fin >> f >> c >> i >> c;
	return 12*f + i;
}

int main() {
	string DFname;
	cout << "Enter file name: ";
	cin >> DFname;
	int L1 = readLength(name);
	int L2 = readLength(name);
	cout << L1 - L2 << "\" difference" << endl;
	return 0;
}

When I run this on the file data.txt from above, the result is

 0" difference
This is not what I want. What happened?
  1. The first call to readLength creates the variable fin, opens data.txt and attaches it to fin, and reads in the first line, i.e. 3' 2".
  2. When the call is over, the stream is closed and fin, being a local variable, is destroyed.
  3. Then we go through the second call to readLength, and the exact same thing happens. In other words, readLength creates the variable fin, opens data.txt and attaches it to fin, and reads in the first line, i.e. 3' 2".
  4. So both calls read in the first line of the file, the second line is never read, and in main L1 and L2 both have the same value!

The moral of the story is this:

Passing a file name to a function makes sense only if you want that one function call to completely process the file.
If each function call is only going to process a piece of the file, and if a later call should pick up in the file where the earlier call left off, you can't pass the file's name. Instead, you nead to pass the file stream to the function.

Passing file streams to functions

So, to get the above scheme to work, main should be the one that creates an ifstream object and attaches it to data.txt, and that ifstream object is what should then be passed to readLength in the two function calls.
Prototype and definition Main

int readLength(ifstream &fin);

int readLength(ifstream &fin) {
	int f, i;
	char c;
	fin >> f >> c >> i >> c;
	return 12*f + i;
}

int main() {
	string name;
	cout << "Enter file name: ";
	cin >> DFname;
	ifstream DFin(name.c_str());
	int L1 = readLength(DFin);
	int L2 = readLength(DFin);
	cout << L1 - L2 << "\" difference" << endl;
	return 0;
}

Now we have a working program, one that prints out 3" when we run it on the file data.txt from above, just like it's supposed to.

Notice that readLength takes the parameter fin by reference.

Why?

So, input and output streams are always passed by reference!

Why giving your parameters type istream is more flexible than ifstream

Now we have a program that works, and we have this readLength function that is potentially useful in other programs as well.
Can we make readLength even more usefull than it already is?
Recall: This (and the analogous output situation) is our one and only example of what are called subtypes. How can something be of type istream and type ifstream at the same time? Well, you manage to be of type "college student" and "midshipman" at the same type. It's possible because "midshipman" is a specific kind of "college student". In other words, "midshipman" is a subtype of "college student".

The readLength function doesn't use anything specific to ifstream that other istream objects don't also have or do. Therefore, we could simply rewrite readLength so that it takes an istream object.

Prototype and definition Main

// istream intead of ifstream!!
int readLength(istream &in); 

int readLength(istream &in) {
	int f, i;
	char c;
	in >> f >> c >> i >> c;
	return 12*f + i;
}

int main() {
  cout << "Enter a length: ";
  int L1 = readLength(cin);   // read from cin!!

  string name;
  cout << "Enter file name: ";
  cin >> DFname;
  ifstream DFin(name.c_str());
  int L2 = readLength(DFin);
  cout << L1 - L2 << "\" difference" << endl;

  return 0;
}

Everything works just like before. So what's the advantage? Well, since cin is an istream object (though not an ifstream object), we can read a length from the keyboard with readLength by the function call readLength(cin). So now we have a readLength that's even more powerful because it can read from files and from the keyboard.

What's the moral of this story? If you have a function that reads (or writes) information, and if the function doesn't use any ifstream (or ofstream) specific things like .open or .close, define your function to take an argument of type istream (or ostream). This way it can read (or write) to files and to the screen. BTW: remember to pass by reference!

Reason 3: When copying a large object is expensive...

We haven't seen yet any explicit examples of "large" objects for which this is important. Our usual types - int, double, char - are all small, so there is no advantage (actually there's a small disadvantage!) to using pass by reference instead of pass by value.

Composing Functions

Let's suppose that I had the function max defined as

int max(int a, int b) {
  return (b > a) ? b : a;
}
but that my program had three ints, x, y and z, amongst which I need the largest. Were I to write
max(x,y,z)
the compiler would complain ... the only max function it knows about only takes two arguments! However, I could say the following:
max(max(x,y),z)
This is our first example of composition of functions. When the function max gets called, its two argument expressions are evaluated. The first is max(x,y), which evaluates to the larger of the two values, and the second is simply z. So, what we get out of this is the maximum of all three values.

The most important thing I can tell you about composing functions, is that there is really nothing to talk about. Function arguments are given in your code by expressions, right? And those expressions are evaluated before calling the function to produce the argument objects that are passed to the function. So, whether or not the argument expressions themselves contain function calls is immaterial — the system works the same.

Problems

  1. Incrementing a Military Clock (You might prefer this solution.) This is a simple pass-by-reference example that modifies its argument.
  2. Reading Binary Numbers Here's a simple example in which we use pass-by-reference to avoid making a copy of an istream object.

Do you know...

Give the output of each of the code snippet:

int a = 3;
int b = a;
a++;
cout << "a: " << a << "b: " << b << endl;
b++;
cout << "a: " << a << "b: " << b << endl;
a = a+b;
cout << "a: " << a << "b: " << b << endl;
b = 2*a;
cout << "a: " << a << "b: " << b << endl;

int a = 3;
int& b = a;
a++;
cout << "a: " << a << "b: " << b << endl;
b++;
cout << "a: " << a << "b: " << b << endl;
a = a+b;
cout << "a: " << a << "b: " << b << endl;
b = 2*a;
cout << "a: " << a << "b: " << b << endl;