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.

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

75 comments to 6.11 — Reference variables

  • 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?

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter