Search

6.11 — Reference variables

So far, we’ve discussed two basic variable types:

  • Normal variables, which hold values directly.
  • Pointers, which hold the address of another value (or null) and can be dereferenced to retrieve the value at the address they point to.

References are the third basic type of variable that C++ supports. A reference is a type of C++ variable that acts as an alias to another object or value.

C++ supports three kinds of references:

  1. References to non-const values (typically just called “references”, or “non-const references”), which we’ll discuss in this lesson.
  2. References to const values (often called “const references”), which we’ll discuss in the next lesson.
  3. C++11 added r-value references, which we cover in detail in the chapter on move semantics.

References to non-const values

A reference (to a non-const value) is declared by using an ampersand (&) between the reference type and the variable name:

In this context, the ampersand does not mean “address of”, it means “reference to”.

References to non-const values are often just called “references” for short.

References as aliases

References generally act identically to the values they’re referencing. In this sense, a reference acts as an alias for the object being referenced.

Let’s take a look at references in use:

This code prints:

7
8

In the above example, ref and value are treated synonymously.

Using the address-of operator on a reference returns the address of the value being referenced:

Just as you would expect if ref is acting as an alias for the value.

A brief review of l-values and r-values

Way back in lesson 1.3 -- A first look at variables, initialization, and assignment, we talked about l-values and r-values, and told you not to worry too much about them. We’re finally at a point where it becomes useful to use those terms again.

To recap, l-values are objects that have a defined memory address (such as variables), and persist beyond a single expression. r-values are temporary values that do not have a defined memory address, and only have expression scope. R-values include both the results of expressions (e.g. 2 + 3) and literals.

References must be initialized

References must be initialized when created:

Unlike pointers, which can hold a null value, there is no such thing as a null reference.

References to non-const values can only be initialized with non-const l-values. They can not be initialized with const l-values or r-values.

Note that in the middle case, you can’t initialize a non-const reference with a const object -- otherwise you’d be able to change the value of the const object through the reference, which would violate the const-ness of the object.

References can not be reassigned

Once initialized, a reference can not be changed to reference another variable. Consider the following snippet:

Note that the second statement may not do what you might expect! Instead of reassigning ref to reference variable value2, it instead assigns the value from value2 to value1 (which ref is a reference of).

References as function parameters

References are most often used as function parameters. In this context, the reference parameter acts as an alias for the argument, and no copy of the argument is made into the parameter. This can lead to better performance if the argument is large or expensive to copy.

In lesson 6.8 -- Pointers and arrays we talked about how passing a pointer argument to a function allows the function to dereference the pointer to modify the argument’s value directly.

References work similarly in this regard. Because the reference parameter acts as an alias for the argument, a function that uses a reference parameter is able to modify the argument passed in:

This program prints:

5
6

When argument n is passed to the function, the function parameter ref is set as a reference to argument n. This allows the function to change the value of n through ref! Note that n does not need to be a reference itself.

Best practice: Pass arguments by non-const reference when the argument needs to be modified by the function.

The primary downside of using non-const references as function parameters is that the argument must be a non-const l-value. This can be restrictive. We’ll talk more about this (and how to get around it) in the next lesson.

Using references to pass C-style arrays to functions

One of the most annoying issues with C-style arrays is that in most cases they decay to pointers when evaluated. However, if a C-style array is passed by reference, this decaying does not happen.

Here’s an example (h/t to reader nascardriver):

Note that in order for this to work, you explicitly need to define the array size in the parameter.

References as shortcuts

A secondary (much less used) use of references is to provide easier access to nested data. Consider the following struct:

Let’s say we needed to work with the value1 field of the Something struct of other. Normally, we’d access that member as other.something.value1. If there are many separate accesses to this member, the code can become messy. References allow you to more easily access the member:

The following two statements are thus identical:

This can help keep your code cleaner and more readable.

References vs pointers

References and pointers have an interesting relationship -- a reference acts like a pointer that is implicitly dereferenced when accessed (references are usually implemented internally by the compiler using pointers). Thus given the following:

*ptr and ref evaluate identically. As a result, the following two statements produce the same effect:

Because references must be initialized to valid objects (cannot be null) and can not be changed once set, references are generally much safer to use than pointers (since there’s no risk of dereferencing a null pointer). However, they are also a bit more limited in functionality accordingly.

If a given task can be solved with either a reference or a pointer, the reference should generally be preferred. Pointers should only be used in situations where references are not sufficient (such as dynamically allocating memory).

Summary

References allow us to define aliases to other objects or values. References to non-const values can only be initialized with non-const l-values. References can not be reassigned once initialized.

References are most often used as function parameters when we either want to modify the value of the argument, or when we want to avoid making an expensive copy of the argument.

6.11a -- References and const
Index
6.10 -- Pointers and const

96 comments to 6.11 — Reference variables

  • pete

    Hi Alex,
    is there a way to pass (through the function parameters) the length of the array along with the array itself? (or it's reference more presicely). I find it very limiting when trying to modulate functions that deals with arrays.
    once again, thanks you for these amazing tutorials, really not taken for granted.

    • nascardriver

      Hi pete!

  • Peter Baum

    I was thinking about how simple * and & are when used on the right side of an assignment, yet it was confusing to me sometimes when used on the left side.  More generally, I wondered what kinds of things made this lesson confusing to me and perhaps others.  Here are some ideas.  

    ****Symbols used in Algebra have very different meaning in C/C++ ****
    Most people learn about the equal sign and variables in algebra.  It can be confusing to find that the equal sign and variables have a different meaning in C/C++.  Variables are really more like structures, with address, mutability, volatility, type, etc. attached to it besides a value.

    ****    "*" and "&" have inconsistent meaning ****
    This goes way beyond their dual use in multiplication and bit manipulation.  Expressions like *p and *a in a declaration can have a completely different meaning when used to the right of an equal sign.  *p and &a are very simple when used outside of a declaration.  &a is particularly problematic because of the many (seemingly arbitrary) rules that apply.  Isn’t there a better way to create an alias?

    ****lvalue and rvalue****

    One way to confuse people is to have two or more incompatible definitions.  We have this situation with lvalue and rvalue.

    1. By their name, one might suppose that lvalue has something to do with values to the left of the = sign and rvalue something to do with values to the right of the = sign.  A mention of this history might help.

    2. In lesson 6.11 we find
        a. l-values are objects that have a defined memory address (such as variables), and persist
           beyond a single expression.
        b. r-values are temporary values that do not have a defined memory address, and only have
           expression scope.

    3. At https://docs.microsoft.com/en-us/cpp/cpp/lvalues-and-rvalues-visual-cpp we find this careful definition
        a. An lvalue is a glvalue that is not an xvalue.
        b. An rvalue is a prvalue or an xvalue.

    In other words
        a. An lvalue is an expression whose evaluation determines the identity of an object, bit-
           field, or function but does not denote denotes an object or bit-field whose resources can
           be reused (usually because it is near the end of its lifetime).
        b. An rvalue is an expression whose evaluation initializes an object or a bit-field, or
           computes the value of the operand of an operator, as specified by the context in which it
           appears or is an expression whose evaluation determines the identity of an object, bit-
           field, or function and denotes an object or bit-field whose resources can be reused.

    4. Although not a definition, we find as potential characteristics at https://docs.microsoft.com/en-us/cpp/cpp/lvalues-and-rvalues-visual-cpp that
        a. An lvalue has an address that your program can access.
        b. An rvalue  has no address that is accessible by your program or has an address that no
           longer accessible by your program but can be used to initialize an rvalue reference, which
           provides access to the expression.

    Note how these characteristics are different from those in 2 above.

    The webpage https://www.quora.com/What-is-lvalue-and-rvalue-in-C is helpful because it talks about what the compiler and cpu are actually doing.

    Here we have an explanation that suggests why things got so complicated: https://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues

    Here is a pretty good description that takes us from C through C++11 and move semantics: https://eli.thegreenplace.net/2011/12/15/understanding-lvalues-and-rvalues-in-c-and-c/

  • Silviu

    Hello,

    Using references it's good for reducing the memory use ? I think that using references can save a little memory where you need it.

    • nascardriver

      Hi Silviu!

      > Using references it's good for reducing the memory use ?
      Yes

      > I think that using references can save a little memory where you need it
      More than a little in some cases.
      Imaging you had a variable that's 4 megabytes (Maybe a database, something big) and you want to pass it to a function. Passing it by value would require another 4 megabytes to hold the copy. Passing it by reference only requires 4 or 8 bytes, depending on the architecture (x86, x64).
      That's just 0.0000954% or 0.0001907% of the original memory consumption.

  • J

    How's it going Alex? Just learned how to use a ref to a array. My question is if you use the ref to an array as function parameters will you still be able keep the size info inside the function or will it still revert to a pointer.

    • nascardriver

      Hi J!
      Usually when passing an array as a function argument it will deduced to a pointer, losing  the ability to get it's size. If you want to pass it by reference you will need to explicitly state the array's size in the function parameter, making the sizeof call redundant, because you already need to know the array's size when writing the function.
      You could use a template to accept custom size arrays, that's an ugly solution though.

      Lesson 6.15 (std::array) and 6.16 (std::vector) will sure come in handy for you. Those are the c++ ways of dealing with arrays.

      • J

        Yea that's what I use but was just wondering if a ref to a array would work also. But I agree std::array and std::vector functions are the way to go thanks for the response

        • Alex

          Yes, you can use a reference to an array and it will keep the type information (from which the size can be derived).

          • david

            Alex can you give an example of that please?
            and how to pass an array to a function by reference?

            • nascardriver

              Hi david!

  • Wunna

    I have a question, but I don't know how to put it.
    To put it simply,how can we do this?

    It prints &x and &ref the same address but &ptr is not.
    Does it means ptr itself was assigned to a memory location?
    By my code, int x strictly means we request a location for the variable x which will be assigned later.
    Which also goes the same for &ref,but since ref itself is the referenced variable itself, it cannot be assigned to a new rvalue, so int &ref = 5 will give compiler error.
    In this case initialization does something like "we want a location or variable to be assigned(or not) to that rvalue(for variable) or lvalue(for an address)"?
    In other words,when we do "int &ref = x;" we are actually assigning &ref to an integer. since & is an operator which is used to get the address of a declared variable, ref have to be a variable itself due to the nature of & operator?

    • Alex

      x, ptr, and ref all occupy discrete locations in memory (though in actuality, the reference may be optimized away). References are usually implemented using pointers, but when you say &pointer, you get the address of the pointer itself, whereas &reference gives you the address of the thing the reference is referencing, not the reference itself.

      When you do int &ref = x, you're telling the compiler that identifier ref is an alias for x. &ref will always give you the address of x, not ref. It's up to the compiler whether to allocate memory for ref or not.

      • Wunna

        But when I did int &ref = *ptr; it wasn't compile error, it means *ptr was treated as l-value?
        Here I thought *(expression) and &(expression) will give you the r-value.

        • Alex

          Yes, *ptr is treated as an l-value. *ptr is a region of storage that can be manipulated, so it qualifies as an l-value.
          &expression (used in the address-of context) is an r-value, because it is returning a value (an address).

  • umang

    can a reference variable be initialized with another reference variable?

    • Alex

      Yes. The reference variable on the right is treated as if it were the thing it's referencing.

  • nikos-13

    If using the address-of operator on a reference returns the address of the value being referenced, then there is no way we can get the actual address of a reference?

      • Peter Baum

        I do not understand this response.  I believe you can get the actual address of a reference by using &.  Here is an example:

        which then outputs something like

        value = 4   alias=4    &alias=001AFB84    &value=001AFB84

        • nascardriver

          Compare the addresses, they're the same. The operator& applied to a reference returns the address of the referenced variable.

          • Peter Baum

            Yes... which is exactly what my comment intended.  My comment was in response to the statement by nikos-13, which was "...that then there is no way we can get the actual address of a reference...".  Alex agreed and I disagreed.

            I am going to think some more about the * and & operators because enough people have gotten confused about them for me to think that there is some fundamental problem that is causing difficulties.

            • Alex

              Taking the address of a reference gives you the address of the object being referenced.

              So when you take the address of the reference, you aren't actually getting the address of the reference itself. FWIW, references are typically implemented as pointers -- they aren't just an alias (although they appear to be when using them). There's no way to get the address of that pointer.

              See this thread for more context.

              • Peter Baum

                Hi Alex,

                I didn't find anything in the lesson that led me to think of the alias as being anything other than an alias, in other words, a convenience for the programmer but treated as just another name for the target by the compiler.  So perhaps it would be helpful if the lesson (or a later lesson) explains why "they aren't just an alias."

                The thread you supplied was great... thank you.  I also found it interesting that different compilers treat this differently.  

                In general, one thing that would help me in these lessons would be more talk about what the compiler is actually doing.  Also helpful would be more information about the evolution of the language.  In particular, some of the extensions in later versions SEEM to make unnecessary clutter, make it harder to learn and understand, and will limit the ability to make necessary extensions in the future.  I'm sure at least some of these additions are justified, but more information about them would be helpful.

                You might also want to consider moving this alias capability to later in the lesson sequence.

                • Alex

                  Thanks for the feedback. It's often not useful to talk about what the compiler is doing, because different compilers may be doing different things. I also try not to get too into the weeds unless it's to help inform a side-conversation such as this one. If you want to know more information, you can almost always find it on a reference site, or via a google search anyway.

                  Why do you think the aliases section should be moved? I think it's useful to talk about what references do in their most primitive form before getting into the more advanced cases...

                • Peter Baum

                  Hmmm... no reply button on your last post Alex, so I'll do it this way.  I suggested the move of the aliases section because at the point where it was introduced it didn't seem to have a very useful purpose.  

                  I have a hard time keeping arbitrary rules or multiple ways of doing the same thing in my head.  If I have a task and an appropriate tool, then it is much easier for me to remember.  It may just be me, but perhaps others feel the same way.

Leave a Comment

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