References in C++
A const reference lets a function read an argument without copying it, while forbidding changes to the original variable through that name.
A Function That Only Reads Its Input
Many functions only need to look at the data they receive. A function may 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 real design decision, because it determines both what the function is allowed to do to the argument and how much work the call performs.
- The parameter is a const reference to a vector of doubles.
- The loop reads each element and adds it to a running total.
- The function returns the total and never changes values.
What do you think happens?
Suppose someone edits the body of sum so that it calls values.push_back(1.0) before the loop. Will the function compile?
Reveal answer
No, the compiler rejects the modifying line
The parameter is a const reference, so the rule that forbids changes through this name is enforced at the line that tries to modify the vector. Reading the elements in the loop would still compile fine.
Copy Cost Versus Mutation Power
The const reference comes from 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. Passing by value gives the function its own copy, which protects the caller's data but costs the work of duplicating it. Passing by non-const reference avoids that copying, but by itself it lets the function modify the original. Adding const to the reference keeps the saving and removes the ability to change the data.
What the Const Reference Actually Holds
A reference parameter does not hold a separate copy of the vector. The parameter v is another name for the caller's vector, so reads go through to the caller's data. Writes through v are blocked at compile time. The const reference is therefore a promise enforced by the language rather than a convention the programmer has to remember.
Reading the Declaration
The source describes a declaration of the form double sum(const vector<double>&). Each part of that line 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.
Finding the Line That Breaks
When a const reference version of a function fails, the fastest route is to find the exact line where the behavior stops matching what you intended. Here the intended result is the total of the elements. Consider an edited version of sum that tries to change the vector it was given.
- The
push_backcall is the only statement that modifies the parameter. - The loop only reads values, so it is allowed through a const reference.
- The return statement behaves the same as before the edit.
Locating the failing statement
A function sum takes const vector<double>& values. Its body modifies values before summing. Which statement breaks the const rule, and why does the loop still compile?
- 1Identify the restricted name
The parameter values is declared const, so the referred-to vector cannot be modified through the name values.
- 2Check each statement for modification
The declaration of total only creates a local double. The
push_backcall tries to change the vector, which is a modification through values. - 3Check reads
The range-based for loop reads each element and copies its value into x. Reading does not modify the vector, so the loop is allowed.
- 4Locate the divergence
The failure is at the modifying line. The loop and the return statement behave as before, so they are not the cause.
The push_back line is the one that breaks the const rule. The compiler rejects it, and the function does not build.
Choosing a Passing Style
Once the mechanics are clear, the design choice is usually straightforward. If a function only reads its argument and copying would be costly, a const reference expresses both facts in the signature. If the function must modify the caller's data, a non-const reference is the appropriate tool. If the function needs its own working copy that it may change freely, passing by value gives it one, at the cost of duplicating the data.
| Passing style | Copy made | Can the function modify the caller's data? | Caller's data protected |
|---|---|---|---|
| By value | Yes, the argument is duplicated | No, it only changes its own copy | Yes |
| By non-const reference | No | Yes | No |
| By const reference | No | No, changes through the name are blocked | Yes, through this name |
Trade-offs for a read-only function argument
Names That Can Still Change the Vector
A const reference forbids changes only through that name. It does not make the original object constant. If the caller still has a non-const name for the same vector, that name remains able to modify the vector. The restriction belongs to the name that was declared const, not to the object it refers to.
Common Mistakes With Const References
Common mistake Believing that const makes the original vector constant everywhere
What it looks likevector<double> values = {1.0, 2.0}; sum(values); values.push_back(3.0);Why it failsThe const applies to the name the function uses. The caller's non-const name can still modify the vector.
Do this insteadTreat const on a reference parameter as a restriction on that name only.
Common mistake Searching for the compile error in the loop or the return statement
Why it failsThe loop and return only read the parameter, which is allowed through a const reference.
Do this insteadFind the statement that modifies the parameter, such as a push_back or an assignment to an element.
Common mistake Passing a read-only argument by value out of habit
Why it failsPassing by value makes the function work on its own copy, which costs the work of duplicating the data.
Do this insteadWhen the function only reads and the argument is large, use a const reference.
Common mistake Dropping the ampersand and assuming the parameter is still a reference
Practice: Predict, Read, and Choose
A function is declared as double average(const vector<double>& data). Its body computes the sum of data and divides by data.size(). Does this body compile? Explain which statement, if any, breaks the const rule.
Hints
- Check every statement for a call or assignment that modifies data.
- Reading an element or calling
size()does not modify the vector.
Write the signature of a function reverse_copy that reads a vector of doubles and returns a new vector with the elements in reverse order. The function must not modify the caller's vector. Explain what each part of your declaration does, and say which declarator operator you used.
Hints
- The parameter is read-only, so consider a const reference.
- Read the declaration from the name outward to check that the reference applies to the whole const type.
A teammate wants a function that adds 1.0 to every element of its argument and then returns the total. They propose double add_and_sum(const vector<double>& values). Explain why this will not compile and state the passing style that would let the function modify the caller's vector.
Hints
- Find the statement that assigns to an element or calls
push_back. - A non-const reference lets the function change the caller's data.
Key Takeaways
- A const reference lets a function read an argument without copying it, while forbidding changes to the original through that name.
- In a declaration such as double
sum(const vector<double>&), const restricts modification through the name, vector<double> names the referred-to type, and the ampersand makes the parameter a reference rather than a copy. - A const reference violation is found at the statement that modifies the parameter, while reads in the loop and return compile normally.
- Pass by value for a private working copy, by non-const reference when the function must change the caller's data, and by const reference when it only reads large data.