Concepts / Const References

Const References

The unary suffix & in a declaration means reference to, and a reference is another name for an existing object.

  • Programming

Why a Function Should Not Always Get a Copy

Suppose you own a vector of numbers called my_vec, and you want a sorting routine to rearrange it. The routine needs to touch the vector you actually own, not a duplicate that it throws away when it finishes. If the function receives a copy, the sorting happens to the copy, and your original stays unchanged. This is the problem that references solve.

Many functions do not need to rearrange anything. They compute a total, search for a value, or print a report, and they never need to change the caller's data. Even so, the way an argument is passed still matters, because it determines both what the function is allowed to do to the data and how much work the call performs. This article focuses on the tool for the read-only case: the const reference, which avoids copying while forbidding changes. Keep one question in mind as you read: when a function works on a name that refers to my_vec, is it working on my_vec itself or on something that merely looks like it?

Tracing a Name Back to Its Object

Definition

A reference is a second name for an object that already exists. It is introduced with a declaration that places the unary suffix & after the type. The suffix is read as 'reference to', so a declaration such as int& r says that r is a reference to an int.

python
Notes on the code
  • int x = 10 creates the object that both names will lead to
  • int& r = x declares r as a reference to int and initializes it to name x
  • r = 12 is an assignment through the name r, and it writes into x

The declaration must name the object the reference will stand for, because a reference is always tied to something at the moment it is created. This is why the initializer after the equals sign is not optional: a reference without an object to stand for has no meaning. Once the two names are in place, r and x are not two separate values in memory. There is one object, and the two names are two routes to it. Any change made through either name is visible through the other, because both names lead to the same storage.

xint object holding 10rreference name for xnames
When a reference r is bound to the variable x, do r and x occupy separate storage, or do they name the same storage?

Why a Reference Stays Bound to One Object

The initializer also decides the reference's identity for life. A reference cannot be made to refer to a different object after its initialization. Writing r = 25 does not rebind r to some other variable. It assigns 25 into the object that r already refers to, which is x. The binding stays the same, and only the value inside the object changes.

x = 10x = 25r names xr names xassign 25 through r
After r is assigned 25, what has changed, and is r still bound to x?

This fixed binding is useful for reading code. When you see a reference, you can rely on its name always standing for the same object for its whole lifetime. You do not need to track whether it has been pointed somewhere else.

Reading the Declaration Piece by Piece

The declaration double sum(const vector<double>&) has several parts, and each one has a job. The keyword const says the referred-to object cannot be modified through this name. The type vector<double> names what is being referred to. The ampersand makes the parameter a reference rather than a copy. The leading double is the return type, so it describes the value the function gives back, not the parameter.

doublereturn type of the functionsumfunction nameconstthe referred object cannotbe modified through thisnamevector<double>type of the object beingreferred to&declarator operator:reference to the constvector<double>
Which part of this declaration is the return type, which is the parameter type, and which symbols modify which part?

Reading a declaration from the inside out helps. Start at the name, then move outward through each declarator operator. For const vector<double>&, the reference operator applies to the whole const vector<double> type, which is why the parameter is described as a reference to a constant vector of doubles.

Definition

A declarator operator is an operator such as &, the asterisk, or the square brackets when it appears in a declaration. In that position it does not perform arithmetic or indexing. It shapes the type being declared, so T& is a reference to T.

Where the Const Rule Is Enforced

Start with a function that adds up the numbers in a vector. Its parameter is a const reference to a vector of doubles. Read the code as a plain description of behavior: the function reads each element, accumulates a running total, and returns it. The reference avoids copying the vector, and const keeps the function from changing it.

cpp
Notes on the code
  • const vector<double>& v: read-only reference to the caller's vector
  • total accumulates the elements as the loop reads them
  • the loop only reads elements, which the const reference allows

Now suppose someone edits the body so that it tries to change the vector it was given. Before you read on, predict what happens when the code is compiled.

cpp
Notes on the code
  • v.push_back(1.0) is the modifying line
  • the loop and the return statement are unchanged from the working version

The compiler rejects the modifying line. The const on the parameter is enforced there, not in the loop or the return statement, which behave exactly as before. When a const reference version fails, the fastest route is to locate the exact line where the behavior stops matching what you intended. In this example the divergence is the single line that changes v, and removing it restores the function that only reads.

The const reference is therefore a promise enforced by the language rather than a convention the programmer has to remember. Reads go through to the caller's data, and writes are blocked at compile time.

Choosing a Passing Mode for Read-Only Data

Passing by value copies the data and protects the caller, but it costs the work of duplicating it. Passing by non-const reference avoids copying but allows changes to the original. Passing by const reference avoids copying and blocks changes. For a function that only reads its argument, the choice comes down to two questions: does the function need to change the caller's data, and is the argument large enough that copying would be costly?

Passing modeCopies the dataCan modify the caller's objectTypical fit for a read-only function
By valueYesNo, changes stay in the copySmall data where copying is cheap
By non-const referenceNoYesFunctions that must change the caller's data, such as sorting
By const referenceNoNo, writes are rejected at compile timeLarge read-only data such as a vector passed to sum

Three passing modes for a function argument

Mistakes Beginners Make With References

Several misunderstandings come up repeatedly when students first use references. Each one is corrected by a rule stated above.

  • Common mistake

    Believing that assigning to a reference makes it refer to a different object

    What it looks like
    int x = 10; int& r = x; int y = 25; r = y;
    Why it fails

    A reference cannot be made to refer to a different object after its initialization. The assignment writes the value of y into x.

    Do this instead

    Read r = y as 'copy the value into the object r names', and expect x to change while r stays bound to x.

  • Common mistake

    Forgetting to initialize a reference

    What it looks like
    int& r;
    Why it fails

    The initializer is not optional, because a reference without an object to stand for has no meaning.

    Do this instead
    Declare the reference together with the object it names, such as int& r = x;
  • Common mistake

    Using the star prefix to access a reference

    Why it fails

    A pointer needs the star prefix to reach the referred value, but a reference is used by its name alone.

    Do this instead

    Write r = 12 rather than attaching a star to r.

  • Common mistake

    Reading the leading type as the parameter type

    What it looks like
    double sum(const vector<double>& v)
    Why it fails

    The leading double is the return type. The parameter type is const vector<double>&.

    Do this instead

    Read the declaration from the inside out, starting at the name and moving outward through the declarator operators.

  • Common mistake

    Expecting a const reference to allow modification by a non-const method call

    What it looks like

    v.push_back(1.0) inside a function whose parameter is const vector<double>&

    Why it fails

    The const promise forbids changes to the referred object through this name, so the modifying line is rejected.

    Do this instead

    Keep a const reference parameter read-only, or pass by non-const reference if the function must change the data.

Predicting Whether a Read-Only Function Compiles

What do you think happens?

Does double sum(const vector<double>& v) compile if its body calls v.push_back(1.0)?

  • Yes, because the function still returns a total

  • No, the call that modifies v is rejected at compile time

Reveal answer
Answer:

No, the call that modifies v is rejected at compile time.

The const on the parameter forbids changes to the referred vector through the name v. The failure is located on the push_back line, not in the loop or the return statement.

MEDIUM

A function is declared as double average(const vector<double>& v) and its body first calls v.clear() and then sums the elements. Identify the line that fails, explain why it fails in terms of the const reference, and rewrite the body so it computes the average of the original elements without modifying v.

Hints
  • Find the exact line that modifies the parameter, since the restriction is enforced there
  • The const applies to the referred object, so any call that changes that object through v is forbidden
  • A correct body can compute the sum and divide by v.size() without changing v

Key Takeaways

  • The unary suffix & in a declaration means reference to, and a reference is another name for an existing object that must be named at initialization.
  • A reference is used by its plain name, has no star prefix, and cannot be redirected to a different object, so assignment writes into the object it names.
  • A const reference lets a function read an argument without copying it while forbidding changes to the original through that name, and the rule is enforced at the modifying line.
  • For a read-only function, choose by value for cheap data that should not be affected, and const reference for large data that should be read without copying.

Related concepts

More concepts starting with C