7.3 — Passing arguments by reference

While pass by value is suitable in many cases, it has a couple of limitations. First, when passing a large struct or class to a function, pass by value will make a copy of the argument into the function parameter. In many cases, this is a needless performance hit, as the original argument would have sufficed. Second, 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 often suitable, there are cases where it would be more clear and efficient to have the function modify the argument passed in. Pass by reference solves both of these issues.

Pass by reference

To pass a variable by reference, we simply declare the function parameters as references rather than as normal variables:

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:

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:

value = 5
value = 6

As you can see, the function changed the value of the argument from 5 to 6!

Here’s another example:

This produces the output:

x = 5
x = 6

Note that the value of argument x was changed by the function.

Returning multiple values via out parameters

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:

This function takes one parameter (by value) as input, and “returns” two parameters (by reference) as output. Parameters that are only used for returning values back to the caller are called out parameters. We’ve named these out parameters with the suffix “out” to denote that they’re out parameters. This helps remind the caller that the initial value passed to these parameters doesn’t matter, and that we should expect them to be rewritten.

Let’s explore how this works in more detail. First, the main function creates local variables sin and cos. Those are passed into function getSinCos() by reference (rather than by value). This means function getSinCos() has access to the actual sin and cos variables, not just copies. getSinCos() accordingly assigns new values to sin and cos (through references sinOut and cosOut respectively), which overwrites the old values in sin and cos. Main then prints these updated values.

If sin and cos had been passed by value instead of reference, getSinCos() would have changed copies of sin and cos, leading to any changes being discarded at the end of the function. But because sin and cos were passed by reference, any changes made to sin or cos (through the references) are persisted beyond the function. We can therefore use this mechanism to return values back to the caller.

This method, while functional, has a few minor downsides. First, the syntax is a bit unnatural, with both the input and output parameters being put together in the function call. Second, the caller must pass in variables to hold the updated values, which means it must have (or create) parameters to hold these output values even if it doesn’t intend to use them. Finally, it’s not obvious from the caller’s end that sin and cos are out parameters and will be changed. This is probably the most dangerous part of this method (as it can lead to mistakes being made). Some programmers and companies feel this is a big enough problem to advise not passing by reference this way, and using pass by address instead when mixing in and out parameters (which has a clearer syntax indicating whether a parameter is modifiable or not).

Personally, we recommend avoiding mixing input and output parameters for this reason, but if you do so, good documentation on the caller’s side can help.

Limitations of pass by reference

Non-const references can only reference non-const l-values (e.g. non-const variables), so a reference parameter cannot accept an argument that is a const l-value or an r-value (e.g. literals and the results of expressions).

Pass by const reference

As mentioned in the introduction, one of the major disadvantages of pass by value is that all arguments passed by value are copied into the function 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 is undesirable when we want an argument be read-only. 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 through the reference. Consequently, if we use a const reference as a parameter, we guarantee to the caller that the function will not change the argument!

The following function will produce a compiler error:

Using const is useful for several reasons:

  • It enlists the compilers help in ensuring values that shouldn’t be changed aren’t changed (the compiler will throw an error if you try, like in the above example).
  • It tells the programmer that the function won’t change the value of the argument. This can help with debugging.
  • You can’t pass a const argument to a non-const reference parameter. Using const parameters ensures you can pass both non-const and const arguments to the function.
  • Const references can accept any type of argument, including l-values, const l-values, and r-values.

Rule: When passing an argument by reference, always use a const references unless you need to change the value of the argument

Pros and cons of pass by reference

Advantages of passing by reference:

  • References allow a function to change the value of the argument, which is sometimes useful. Otherwise, const references can be used to guarantee the function won’t change the argument.
  • Because a copy of the argument is not made, pass by reference is fast, even when used with large structs or classes.
  • References can be used to return multiple values from a function (via out parameters).
  • References must be initialized, so there’s no worry about null values.

Disadvantages of passing by reference:

  • Because a non-const reference cannot be made to an const l-value or an r-value (e.g. a literal or an expression), reference arguments must be normal variables.
  • It can be hard to tell whether a parameter passed by non-const reference is meant to be input, output, or both. Judicious use of const and a naming suffix for out variables can help.
  • It’s impossible to tell from the function call whether 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.

When to use pass by reference:

  • When passing structs or classes (use const if read-only).
  • When you need the function to modify an argument.

When not to use pass by reference:

  • When passing fundamental types (use pass by value).
  • When passing built-in arrays (use pass by address).
7.4 -- Passing arguments by address
7.2 -- Passing arguments by value

71 comments to 7.3 — Passing arguments by reference

  • The Inquisitive Dude

    Sir, in the following code:

    When I put '&' before withdrawMoney and depositMoney arguments in the methods withdraw and deposit respectively, it throws an error, but it works for money in balance. Why?

    • nascardriver

      Yo Dude!

      The error is caused by you passing r-values to those functions, you cannot have a standard reference to an r-value. If you modified your @main function like so

      it'd work.

      This isn't nice though, that's why you shouldn't pass a simple reference to those functions but a const reference instead.

      Also, don't use 'using namespace x;'

  • Paul

    I am a little confused. sin and cos in  getSinCos are C++ built-in functions, right?   But sin and cos in main function are variables?

    • nascardriver

      Hi Paul!

      You're correct. The variable names are badly chosen, after declaring them you won't be able to use the sin and cos functions in main.

      • Alex

        I'm not sure I'd say the variable names are badly chosen -- they reflect exactly what those things are meant to be. You can still use the C-style sin and cos functions in main by prefixing them with the :: scope resolution operator, to indicate that you want to use the global function, not the local variable.

        That said, we really should be using the standard library version of these functions since this is C++. I've updated the example accordingly.

  • Rasikko

    I've read that you should only pass a struct/class to a function by reference. Does this apply to their member variables/functions as well?

    • nascardriver

      Hi Rasikko!

      To answer your question you'll need a little bit of background knowledge.
      Your computer is good at handling data types with a size of 2^n bits. That is 1, 2, 4, 8, 16, 32, 64 and so on. It stops being good at types larger than 32 bits on x86 or 64 bits on x64 systems.
      Structs and classes are usually larger that 32 or 64 bits or they have an odd size, thus making your program run slower than it could be. On top of that you'll create a copy of the object every time you pass it to a function without passing it by reference (copying memory is slow).
      By passing a reference you're not creating a copy, instead you're only passing the address of the object you want to pass. That address is exactly 32 or 64 bits in size, making it great for your computer to work with.

      If your member variable has an odd size you should pass it by reference.
      All the standard numeric types (char, int, long, float, double, ...) can be passed by value, because they have 2^n sizes. Anything other than those should be passed by
      (const) reference.

      • Rasikko

        Ok, I think I understand. My machine is 64bit. So it appears it's ok to pass by value(they are all numeric types) and to pass by reference if it's other types or the whole class/struct. Thank you for your answer. I was stuck on that for sometime.

  • Aitor

    Hi Alex,
    i'm trying to understand, why "const int& variableName" as a function parameter works with constant literals, it is a thing that always confuse me. So having the code below:

    we could say, that both expressions are, in some way, an r-value references but the only difference between the first and the second one, is the first one (const int& i) cannot be modify, and the second one (int&& x) can be modify? or i'm just totally confused. Thanks for your time.

    Best regards, Aitor

    • Alex

      Both of the above are l-value references. The first is a const l-value reference assigned to r-value 10. In the latter, the second ampersand is essentially ignored -- named r-value references are l-values! So this becomes non-const l-value reference assigned to r-value 10. Remember, l-value and r-value are properties of expressions, not variables. We use the term r-value reference not to denote that the variable is an r-value itself, but to denote the type of expressions that it can reference.

  • ty

    Hi, dear Alex, May I ask when you say

    Const references can accept any type of argument, including l-values, const l-values, and r-values.

    the r-values. How come when not pass by const reference we cant recevied the r-values but when we declare a const parameter we can pass the r- values? r-value are literals right? they do not have address am i right thanks so much!

    • Alex

      r-values can be literals or temporary values. When you bind a const reference to an r-value, the duration of the r-value is extended to match the reference. This is useful because it allows us to pass r-values to functions by reference! Otherwise we'd have to pass them by value, which requires making a copy (or in C++11, by r-value reference).

  • D Brown

    In chapter 6 on question 2 I believe in the solution you passed a structure to the function and it was void because it changed the argument. I was very confused since I'm not sure we had done this before. Would be it be appropriate to add a note since its explained after the fact? I apologize if it is explained before that.

  • KG

    You have written: "Some programmers and companies feel this is a big enough program to advise not passing by reference this way..."

    I'm guessing you meant to write "big enough problem..." instead?

    This is all fantastic stuff by the way, I've been meaning to learn C++ for quite some time but always felt a little intimidated, but perhaps it's not quite as complicated as I had led myself to believe.


    Hello Alex. I refer learn c++ almost everyday. I respect you as a ubiquitous tutor.  
    You say that
    Disadvantages of passing by reference:
    Because a non-const reference cannot be made to an non-const l-value or an r-value (e.g. a literal or an expression)

    But, I think that
    Because a non-const reference cannot be made to an const l-value or an r-value (e.g. a literal or an expression)

    Is it correct or not?

Leave a Comment

Put all code inside code tags: [code]your code here[/code]