Search

8.5 — Pass by reference

In the prior lessons, we covered the basic mechanics of l-value references and l-value references to const, but in isolation, these may not have seemed very useful. In this lesson, we’ll finally provide our first answer to the question about what makes references useful, and you’ll subsequently start seeing them everywhere.

First, some context. Back in lesson 2.3 -- Introduction to function parameters and arguments we discussed pass by value, where an argument passed to a function is copied into the function’s parameter:

In the above program, when printInt(x) is called, the value of x (2) is copied into parameter y.

At the end of the function, object y is destroyed. This means we made a copy, only to use it briefly and then destroy it! Fortunately, because fundamental types are cheap to copy, it’s not a problem to make copies and discard objects of these types.

However, in lesson %Failed lesson reference, id intro%, we noted that most of the types in the standard library are class types. It turns out that class types are often expensive to copy, so we generally want to avoid making unnecessary copies (especially ones we’ll destroy almost immediately).

Consider the following program illustrating this point:

This prints

Hello, world!

While this program functions like we expect, it’s also inefficient. std::string is a class type, and copying a std::string is particularly expensive. So creating a copy of argument s every time we call function printString() isn’t great. We can do better.

Pass by reference

One way to solve this problem is to use pass by reference instead of pass by value. When using pass by reference, we declare a function parameter as a reference (or const reference) rather than as a normal variable. Then, when the function is called, each reference parameter binds to the appropriate argument, and the function accesses the argument through the reference. This avoid making a copy of the argument.

Here’s the same example as above, using pass by reference instead of pass by value:

Now, when printString(s) is called, reference parameter y is bound to argument s. Since a reference to an object is treated the same as the object itself, when printString uses reference y, it’s accessing the actual object s, not a copy!

The most notable consequence of pass by reference is that no copy of the argument is made when the function is called. Therefore, we typically pass class types by reference to avoid the performance cost of making unnecessary copies.

Key insight

Pass by reference allows us to pass arguments to functions without making copies.

Because binding a reference to an object and copying a fundamental type cost about the same, we typically do not pass fundamental types by reference (unless there is a specific reason to do so).

Best practice

Prefer passing class types (structs and classes) by (const) reference (to avoid making an expensive copy), and fundamental types by value.

Pass by reference allows us to change the value of an argument

Because reference parameters act identically to the object being referenced, any changes made to the reference will affect the argument:

This prints:

5
6

In the above program, value initially has value 5. However, value is passed by reference to function addOne, which uses the reference to change the value of value from 5 to 6. This updated value persists even after addOne has terminated.

This ability to have functions affect the value of arguments is the primary reason to pass values by l-value reference to non-const.

Key insight

Passing values by reference to non-const allows us to write functions that modify the value of arguments passed in.

One downside of non-const reference parameters is that they can only accept arguments that are modifiable l-values (essentially, non-const variables). In practical terms, that limits their use. What happens if we want to pass a const variable, or even a literal by reference? The following program won’t work:

Pass by const reference

We can also use l-value references to const values as function parameters. This offers the same benefit as pass by reference (avoiding making a copy) while guaranteeing that the function can not change the value being referenced. In most cases, this is what we want.

L-value references to const values are particularly useful as function parameters because of their versatility. Because an l-value reference to a const value can bind to a modifiable l-value, a non-modifiable l-value, or an r-value, a const reference parameter can accept all of these kinds of arguments (including literals and the results of expressions):

The above prints:

1234

Rule

Favor passing by const reference over passing by non-const reference unless you have a specific reason to do otherwise (e.g. the function needs to change the value of an argument).

Mixing pass by value and pass by reference

A function with multiple parameters can set whether each parameter is passed by value or passed by reference individually.

For example:

In the above example, x is passed by value, and s is passed by reference.


8.6 -- Pointers
Index
8.4 -- L-value references to const

32 comments to 8.5 — Pass by reference

  • I NOTICED THAT DE PROGRAM DOES NOT GIVE THE DE CORRECT ANSWER OF COS 30 AND SIN 30

    .... R ...

    I LEARNT A NEW LESSON AS WELL.

    THANKS

  • JohnM

    I copied and pasted the following into Microsoft Visual C++ 2010 Express and ithe output is not correct. The output for 45 degress is show below my program.

    Can you help me understand why?

    // Passing_multiple_arguments.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"
    #include
    #include // for sin() and cos()

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

    int _tmain(int argc, _TCHAR* argv[])

    {
    double dSin = 0.0;
    double dCos = 0.0;

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

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

    return 0;
    }

    /*
    OUTPUT is:

    The sin is 0.850904
    The cos is 0.525322

    */

    • JohnM

      For some reason this query doesn't show that #include and #include were included; however, they were in the program when I ran it.

    • papagym177

      If this program is supposed to print out the sin and cos of 30 degrees it doesn't do that.
      The code is wrong.

      Here are figure from my Casio Calc.
      For 30 degrees the sin is 0.5, cos is slightly > 0.886.

      For 45 degrees the sin and cos are equal slightly > 0.707. So John M's output is wrong too.

      I'm just learning C++ too, so maybe someone can give use the right code to do that.

      One more thing, if you add-> using namespace std; to the top of this code. You can delete all of std:: in the code. (Smile)

  • etam

    "Rule: Always pass by const reference unless you need to change the value of the argument"
    It's not quite true. When passing small things like built-int types, or small structures like a pair of built-in types, it's faster to copy them, than to pass reference or pointer. It allows compiler for better optimizations.

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">