Search

8.7 — Pass by reference and return 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 answer 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 doSomething(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 (and enumerations) 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).

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 function parameters as references (or const references) rather than as a normal variables. 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 doSomething(x) is called, reference parameter y is bound to argument x. Since a reference to an object is treated the same as the object itself, when doSomething uses reference y, it’s accessing the actual object x, 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 paying the performance cost of making unnecessary copies. Although we haven’t introduced any class types yet, we’ll do so shortly, and now you’ll understand why we pass these by reference rather than by value!

Key insight

We typically pass class types by reference to avoid making unnecessary, expensive copies.

Best practice

Prefer passing class types (structs and classes) by (const) reference, and fundamental types (and enumerations) by value.

Author's note

Because we haven’t introduced any class types yet, in this lesson only, we’ll demonstrate pass by reference using fundamental types.

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

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

This prints:

5
6

In the above program, code 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.

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?

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.

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 bind to all of these kinds of arguments (including literals and the results of expressions):

The above prints:

1234

Mixing pass by value and pass by reference

A function can mix pass by value and pass by reference:

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

In and out parameters

Parameters passed by value or const reference are sometimes called in parameters (short for input parameters) because such parameters are used to send values into the function (the function uses those values as inputs).

However, parameters passed by non-const reference allow a function to modify the value of the argument. If a function modifies the value of an argument, it is, in effect, outputting a value.

In-out parameters (short for input and output parameters) are parameters where the function both uses and changes the value of the parameter. The addOne function we showed in a prior example has an in-out parameter:

Out parameters (short for output parameters) are parameters that the function only uses for outputting value. Out parameters are sometimes labeled with an “out” or “Out” prefix or suffix to indicate their output-oriented nature. This can help remind the caller that the initial value passed to these parameters will be overwritten. By convention, out parameters are typically the rightmost parameters.

Here’s an example of a program that uses output parameters:

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

The downsides using references for out and in-out parameters

While out and in-out parameters might seem like a convenient way to return multiple values, they have a few downsides. First, the caller must pass in arguments to hold the outputs even if the caller doesn’t intend to use them. More importantly, the syntax is a bit unnatural, with both the input and output parameters being put together in the function call. This can make it easy to overlook that some arguments might be modified.

Some programmers and companies frown upon using non-const references for out and in-out parameters because the calling syntax offers no clue that the function will change the value. For example:

And we blew up the world again! We really need to kick this habit.

Note that there’s no easy way to tell by scanning the function calls that mode is an in parameter for function doSomething and an in-out parameter for function doSomethingElse. They look identical. This makes out and in-out parameters passed by non-const reference potentially dangerous, because a value could be changed without the programmer realizing that was possible.

For advanced readers

This syntax issue can be addressed by using pass by address (using pointers) instead.

Although out parameters offer one way for a function to return multiple values, this use is generally frowned-upon, as there are better ways to return multiple values.

Best practice

Avoid out parameters (use other methods to return multiple values), and avoid in-out parameters when practical.

For advanced readers

Besides in, in-out, and out parameters, there are two other parameter “types”: consume parameters, and forward parameters. Both of these are related to r-value references and move semantics, which we’ll talk about in a future chapter.

Return by reference

When passing by value, a copy of the argument is made into the parameter. Because this can be expensive for class types, we often choose pass by reference instead, to avoid making a copy.

Similarly, when using return by value, a copy of the return value is passed back to the caller. For class types, this can be similarly expensive. In such cases, we may (or may not) want to return by reference instead (which avoids the copy).

To return by reference, we simply define the return value of the function to be a reference type:

Using return by reference has one major caveat: the programmer _must_ be sure that the returned object lives beyond the scope of the function returning the reference.

Consider the following example that returns a local variable by reference:

What’s the result of this program? It’s indeterminate. When someFunction returns, a reference to local variable i is returned. Then, because i is an automatic variable, i is destroyed at the end of the function. The means the returned reference is now dangling, and any use of the reference will result in undefined behavior.

Warning

Never return a local variable by reference.

Typically, references can be safely returned in one of three cases:

1) When returning a parameter passed in by reference:

In the above function, the caller must have passed in a valid object for x and y, therefore it’s safe to assume that if we return x or y, those objects will still exist.

2) When returning a static variable (either a local static, or a global variable). This doesn’t happen very often.

3) When returning a member of an object (including *this) from a member function. We’ll cover this case in a future chapter, when we talk about creating your own objects.


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

32 comments to 8.7 — Pass by reference and return 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="">