Navigation



7.3 — Passing arguments by reference

Pass by reference

When passing arguments by value, the only way to return a value back to the caller is via the function’s return value. While this is suitable in many cases, there are a few cases where better options are available. One such case is when writing a function that needs to modify the values of an array (eg. sorting an array). In this case, it is more efficient and more clear to have the function modify the actual array passed to it, rather than trying to return something back to the caller.

One way to allow functions to modify the value of argument is by using pass by reference. In pass by reference, we declare the function parameters as references rather than normal variables:

void AddOne(int &y) // y is a reference variable
{
    y = y + 1;
}

When the function is called, y will become a reference to the argument. Since a reference to a variable is treated exactly the same as the variable itself, any changes made to the reference are passed through to the argument!

The following example shows this in action:

void foo(int &y) // y is now a reference
{
    using namespace std;
    cout << "y = " << y << endl;
    y = 6;
    cout << "y = " << y << endl;
} // y is destroyed here

int main()
{
    int x = 5;
    cout << "x = " << x << endl;
    foo(x);
    cout << "x = " << x << endl;
    return 0;
}

This program is the same as the one we used for the pass by value example, except foo’s parameter is now a reference instead of a normal variable. When we call foo(x), y becomes a reference to x. This snippet produces the output:

x = 5
y = 5
y = 6
x = 6

Note that the value of x was changed by the function!

Here is another example:

void AddOne(int &y)
{
    y++;
}

int main()
{
    int x = 5;

    cout << "x = " << x << endl;
    AddOne(x);
    cout << "x = " << x << endl;

    return 0;
}

This example prints:

x = 5;
x = 6;

As you can see, the function was able to change the value of the argument.

Sometimes we need a function to return multiple values. However, functions can only have one return value. One way to return multiple values is using reference parameters:

#include <iostream>
#include <math.h>    // for sin() and cos()

void GetSinCos(double dX, double &dSin, double &dCos)
{
    dSin = sin(dX);
    dCos = cos(dX);
}

int main()
{
    double dSin = 0.0;
    double dCos = 0.0;

    // GetSinCos will return the sin and cos in dSin and dCos
    GetSinCos(30.0, dSin, dCos);

    std::cout << "The sin is " << dSin << std::endl;
    std::cout << "The cos is " << dCos << std::endl;
    return 0;
}

This function takes one parameter (by value) as input, and “returns” two parameters (by reference) as output.

Pass by const reference

One of the major disadvantages of pass by value is that all arguments passed by value are copied to the parameters. When the arguments are large structs or classes, this can take a lot of time. References provide a way to avoid this penalty. When an argument is passed by reference, a reference is created to the actual argument (which takes minimal time) and no copying of values takes place. This allows us to pass large structs and classes with a minimum performance penalty.

However, this also opens us up to potential trouble. References allow the function to change the value of the argument, which in many cases is undesirable. If we know that a function should not change the value of an argument, but don’t want to pass by value, the best solution is to pass by const reference.

You already know that a const reference is a reference that does not allow the variable being referenced to be changed. Consequently, if we use a const reference as a parameter, we guarantee to the caller that the function will not (and can not) change the argument!

The following function will produce a compiler error:

void foo(const int &x)
{
    x = 6;  // x is a const reference and can not be changed!
}

Using const is useful for several reasons:

  • It enlists the compilers help in ensuring values that shouldn’t be changed aren’t changed.
  • It tells the coder whether they need to worry about the function changing the value of the argument
  • It helps the coder debug incorrect values by telling the coder whether the function might be at fault or not

Rule: Always pass by const reference unless you need to change the value of the argument

Summary

Advantages of passing by reference:

  • It allows us to have the function change the value of the argument, which is sometimes useful.
  • Because a copy of the argument is not made, it is fast, even when used with large structs or classes.
  • We can pass by const reference to avoid unintentional changes.
  • We can return multiple values from a function.

Disadvantages of passing by reference:

  • Because a non-const reference can not be made to a literal or an expression, reference arguments must be normal variables.
  • It can be hard to tell whether a parameter passed by reference is meant to be input, output, or both.
  • It’s impossible to tell from the function call that the argument may change. An argument passed by value and passed by reference looks the same. We can only tell whether an argument is passed by value or reference by looking at the function declaration. This can lead to situations where the programmer does not realize a function will change the value of the argument.
  • Because references are typically implemented by C++ using pointers, and dereferencing a pointer is slower than accessing it directly, accessing values passed by reference is slower than accessing values passed by value.
7.4 — Passing arguments by address
Index
7.2 — Passing arguments by value

50 comments to 7.3 — Passing arguments by reference

You must be logged in to post a comment.