Concepts / Pass by Value and Pass by Reference

Pass by Value and Pass by Reference

A const reference lets a function read an argument without copying it, while forbidding changes to the original variable through that name.

  • Programming

A Read-Only Sum Function

Many functions only need to look at the data they receive. A function might compute a total, search for a value, or print a report, and it never needs to change the caller's data. Even so, the way an argument is passed is a design decision, because it determines both what the function is allowed to do to the argument and how much work the call performs.

Consider 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, keeps a running total, and returns it.

cpp
Notes on the code
  • The parameter values is a const reference, so the function refers to the caller's vector instead of receiving a copy of it.
  • The loop only reads each element, which is all this function needs to do.

What do you think happens?

Suppose someone edits the body so that it assigns a new value to the first element of values before the loop runs. What does the compiler do with that line?

  • It accepts the line and the function still returns the total of the elements

  • It rejects the line, so the function does not compile

  • It accepts the line but makes a private copy of the vector first

Reveal answer
Answer:

It rejects the line, so the function does not compile

The const in the parameter forbids modifying the referred-to object through the name values. The language enforces this at compile time, so the edited version produces no program at all.

Two Goals in Tension

The reason for a const reference is a tradeoff between two goals. The first goal is to keep the function from changing the argument. The second is to avoid the cost of copying it. Each of the three common ways to pass an argument serves these goals differently.

Definition

Passing by value gives the function its own copy of the argument. This protects the caller's data, but the call must pay the work of duplicating it. Passing by non-const reference avoids that copying, but by itself it allows the function to modify the original. Passing by const reference keeps the saving from avoiding the copy and removes the ability to change the data through that name.

Where the Parameter's Data Lives

The diagram below traces the single path the data takes. The parameter values does not hold a separate copy of the vector. Reads go through to the caller's data, and writes are blocked at compile time. Because there is only one underlying object, the function does no duplication work when it is called.

readingscaller's variablevector of doublesone object, not a copyvaluesconst reference parameterownsreads through, writes blocked
After the call begins, does the parameter occupy its own memory, or does it refer to the caller's original vector?

Both names lead to the same vector. The caller's variable owns it, and the parameter is a second path to it that is restricted to reading. This is why the const reference saves the copying cost that pass by value would incur, while still preventing the function from changing the data through the parameter name.

Reading the Declaration

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, and sum is the function name.

doublereturn typesumfunction nameconstreferred-to object isread-only through this namevector<double>type being referred to&declarator making it areference
Which token is the return type, which is the name, and what does each remaining token modify?

Operators such as the ampersand have a special role when they appear in declarations. In that position they are called declarator operators. They do not perform any operation at that moment. Instead, they shape the type being declared. The ampersand applied to a type T produces a reference to T. In const vector<double>&, the ampersand applies to the whole const vector<double> type, which is why the declaration describes a reference to a constant vector of doubles.

Best practice

Read a declaration from the inside out. Start at the name, then move outward through each declarator operator. For this declaration, that means starting at values, applying the ampersand to get a reference, and then noting that the reference is to a const vector of doubles.

Tracing the Edit That Fails

When a const reference version of a function fails, the fastest route is to find the exact line where the behavior stops matching your intent. Start from the version that compiles and add one modifying statement, as shown below. The edited line is the only change.

cpp
Notes on the code
  • The assignment values[0] = 0.0 writes through the const reference, which the language forbids. This is the line that fails to compile.
  • The loop and the return statement are unchanged from the working version and behave the same as before.
Worked example

Locating the rejected line

A read-only sum function has been edited to assign to values[0]. Decide which line is rejected and whether the rest of the function is affected.

  1. 1
    Identify the expected result

    The function is supposed to return the total of the elements of values.

  2. 2
    Find the modifying operation

    The statement values[0] = 0.0 changes an element of the vector through the parameter name values.

  3. 3
    Check whether the name allows that change

    The parameter is declared with const, which says the referred-to object cannot be modified through this name. The write is therefore rejected at compile time.

  4. 4
    Check the other lines

    The accumulation loop and the return statement only read the data or use the local total, so they behave the same as in the original version.

Result

The rejected line is values[0] = 0.0. Because the program does not compile, the function produces no total at all, and the divergence is located at that modifying line rather than in the loop or the return.

Mistakes Readers Make With const References

  • Common mistake

    Assuming that a const reference makes the caller's variable constant

    What it looks like

    vector<double> readings = {1.0, 2.0}; readings[0] = 5.0; is still legal after sum has been declared with a const reference parameter.

    Why it fails

    The const belongs to the name used inside the function, so it restricts only the parameter, not the object itself.

    Do this instead

    Treat const as a restriction on that one name. The original variable stays modifiable through its own name.

  • Common mistake

    Reading the & as an operation that takes the address of something

    What it looks like

    In double sum(const vector<double>&), the ampersand is not applied to a variable at that point.

    Why it fails

    In a declaration, the ampersand is a declarator operator that builds a type, here a reference to const vector of doubles.

    Do this instead

    Read the declaration from the inside out, starting at the name, and treat the ampersand as part of the type.

  • Common mistake

    Assuming that a const reference parameter always copies the argument when it is convenient

    Why it fails

    The purpose of the const reference is to avoid copying while still blocking changes, so using it for read-only data is the point of the design.

    Do this instead

    Use const reference when the function only reads a large argument, so the signature expresses both facts.

  • Common mistake

    Searching for the error in the loop when the rejected line is the assignment to the parameter

Choosing a Passing Mode

Once the mechanics are clear, the design choice is usually straightforward. The table compares the three modes for a function that only needs to read its argument. The key questions are how much copying the call performs and whether the function can modify the caller's variable.

Passing modeCopying at the callCan the function modify the caller's variable?Fit for a read-only function
By valueYes, the function gets its own copyNo, changes affect only the copySafe, but costly when the argument is large
By non-const referenceNo copy is madeYes, changes reach the originalAvoids the copy but allows unwanted changes
By const referenceNo copy is madeNo, modification through the name is rejectedUsually the right choice for a large read-only argument

Three ways to pass an argument to a read-only function

Try It Yourself

MEDIUM

A function is declared as double average(const vector<double>& values) and its body computes the mean of the elements. Predict which of these two lines would be rejected by the compiler and explain why: (1) double first = values[0]; (2) values.push_back(0.0);

Hints
  • Ask whether each line reads the element or changes the vector.
  • The line that calls a member function that modifies the vector is the one the const reference forbids.

Key Takeaways

  • Passing by value copies the argument and protects the caller, passing by non-const reference avoids copying but allows changes, and passing by const reference avoids copying while blocking changes through the parameter name.
  • In double sum(const vector<double>&), const says the referred-to object cannot be modified through this name, vector<double> is the referred-to type, and the ampersand makes the parameter a reference.
  • Declarator operators such as the ampersand shape a type in a declaration rather than performing an operation, and they are read from the inside out starting at the name.
  • When a const reference function fails to compile, the restriction is enforced at the exact line that modifies the parameter, not in the surrounding loop or return statement.
  • A const reference does not make the original object constant; the caller's variable can still be changed through its own name.

Related concepts

More concepts starting with P