5.5.  Parameter Passing by Reference

[ fromfile: functions.xml id: passreference ]

Large objects, or objects with expensive copy constructors, should not be passed by value because the creation of copies needlessly consumes machine cycles and time. In C, you can avoid copying objects by passing pointers to them; however, using pointers requires a different syntax from using objects. Further, accidental misuse of pointers can cause data corruption, leading to runtime errors that can be difficult to find and fix. In C++ (and C99), you can pass by reference, a mechanism that offers the same performance as a pass by pointer. With objects, this permits use of the (.) operator for accessing members indirectly.

A reference parameter is simply a parameter that is an alias for something else. To declare a parameter to be a reference, put the ampersand character (&) between the type name and the parameter name.

A reference parameter of a function is initialized by the actual argument that is passed when the function is called. That argument must be, as with any reference, a non-const lvalue. Changes to a non-const reference parameter in the function cause changes to the argument object used to initialize the parameter. This feature can be used to define functions, which can normally return at most one value, that cause changes in several argument objects, effectively enabling the function to return several values. Example 5.13 shows how reference parameters can be used with integers.

Example 5.13. src/reference/swap.cpp

#include <iostream>
using namespace std;

void swap(int& a, int& b) {
    int temp = a;
    cout << "Inside the swap() function:\n"
         << "address of a: " << &a
         << "\taddress of b: " << &b
         << "\naddress of temp: " << &temp << endl;
    a = b;
    b = temp;
}


int main() {
    int n1 = 25;
    int n2 = 38;
    int n3 = 71;
    int n4 = 82;
    cout << "Initial values:\n"
         << "address of n1: " << &n1
         << "\taddress of n2: " << &n2
         << "\nvalue of n1: " << n1
         << "\t\t\tvalue of n2: " << n2
         << "\naddress of n3: " << &n3
         << "\taddress of n4: " << &n4
         << "\nvalue of n3: " << n3
         << "\t\t\tvalue of n4: " << n4
         << "\nMaking the first call to swap()" << endl;
    swap(n1,n2);
    cout << "After the first call to swap():\n"
         << "address of n1: " << &n1
         << "\taddress of n2: " << &n2
         << "\nvalue of n1: " << n1
         << "\t\t\tvalue of n2: " << n2
         << "\nMaking the second call to swap()" << endl;
    swap(n3,n4);
    cout << "After the second call to swap():\n"
         << "address of n3: " << &n3
         << "\taddress of n4: " << &n4
         << "\nvalue of n3: " << n3
         << "\tvalue of n4: " << n4 << endl;
    return 0;
}

<include src="src/reference/swap.cpp" href="src/reference/swap.cpp" id="swapcpp" mode="cpp"/>


There are extra output statements in this program to help keep track of the addresses of the important variables.

Initial values:
address of n1: 0xbffff3b4       address of n2: 0xbffff3b0
value of n1: 25                 value of n2: 38
address of n3: 0xbffff3ac       address of n4: 0xbffff3a8
value of n3: 71                 value of n4: 82

Initially the program stack might look something like Figure 5.1.

Figure 5.1.  Before First swap()

Before First swap()

As the program proceeds, you see output like this:

Making the first call to swap()
Inside the swap() function:
address of a: 0xbffff3b4        address of b: 0xbffff3b0
address of temp: 0xbffff394

When references get passed to functions, the values that get pushed onto the stack are addresses, not rvalues. Under the covers, pass-by-reference is very much like pass-by-pointer. The stack now might look like Figure 5.2.

Figure 5.2.  Inside first swap()

Inside first swap()

After the first call to swap():
address of n1: 0xbffff3b4       address of n2: 0xbffff3b0
value of n1: 38                 value of n2: 25
Making the second call to swap()
Inside the swap() function:

Figure 5.3 shows the state of the stack after the preceding lines have been printed.

Figure 5.3.  Inside Second swap()

Inside Second swap()

address of a: 0xbffff3ac        address of b: 0xbffff3a8
address of temp: 0xbffff394
After the second call to swap():
address of n3: 0xbffff3ac       address of n4: 0xbffff3a8
value of n3: 82 value of n4: 71

The swap() function is actually working with n1 and n2 during the first call, and with n3 and n4 during the second call.

Pass-by-reference syntax provides an alternative to pass-by-pointer. Under the covers, it is implemented with pointers and the value is not copied. The main difference between pass-by-pointer and pass-by-reference is that with a pointer you must dereference it, whereas with a reference you can access it in same way you would access the referred entity.

[Important] Pass-by-Pointer or Pass-by-Reference?

When you have a choice, it is generally preferable to use references instead of pointers because this can reduce the number of places where a programmer can accidentally corrupt memory. It is only when you need to manage objects (creation, destruction, adding to a managed container) that you need to operate on pointers, and those routines can usually be encapsulated as member functions.