Summary
This lesson studies the paths by which data enter a function and its results leave it. By default, C++ passes arguments by value: the parameter is a copy, and nothing the function does to it reaches the original. A reference, on the other hand, is a second name for a cell that already exists, and it lets the function modify the caller’s variable; the const reference lets it read a large datum without copying it and without being able to alter it. To return several results, the library offers std::pair and std::tuple, which are received with structured bindings. Then come the scope of a name (where it is visible) and the storage duration of a variable (when it exists), with the shadowing of a name and static local variables, and the lesson closes with the reference that outlives its variable. The concept that unifies it all is the flow of data between the parts of a program: copying versus sharing, and the visibility of names as a mechanism of isolation.
Learning objectives
- Predict the effect of a function on its arguments according to whether they are passed by value or by reference.
- Choose the passing mode of each parameter (value for small types, const reference for large data, reference for what is modified) and justify the choice.
- Return several results with
std::pairorstd::tupleand receive them with structured bindings. - Determine the scope and the storage duration of each variable of a program, including
staticlocal variables and those shadowed by another. - Recognize the reference to a local variable or to a temporary value that no longer exists, with the compiler warning that announces it, and correct it.
Pass by value
The parameter is a copy
The lesson Functions showed that each call creates a frame on the stack, with the parameters and the local variables of the function, and that the frame disappears on returning. It remains to specify what relation a parameter bears to the argument that initializes it. The default answer of C++ is the simplest one: the parameter is a new local variable, initialized with a copy of the value of the argument. If this is so, a function that modifies its parameters modifies its copies, and the caller’s variables do not change. The following program puts this to the test with a function that tries to swap two integers.
#include <print>// Tries to swap a and b; it only swaps their copies.void swapValues(int a, int b){ const int saved{a}; a = b; b = saved; std::println("Inside: a = {}, b = {}", a, b);}int main(){ int x{1}; int y{2}; swapValues(x, y); std::println("After: x = {}, y = {}", x, y);}Inside: a = 2, b = 1After: x = 1, y = 2Inside the function, the swap is done correctly; after the call, x and y keep their values. The compiler does not warn, because there is nothing to warn about: the code swaps two local variables that disappear when it ends. The figure follows the stack frames during the call.
The frame of main contains x and y; the call creates on top of it the frame of swapValues, with cells of its own.
Proof
Before the call, the frame of
mainhas two cells:xwith 1 andywith 2.The call creates the frame of
swapValues. Its parametersaandbare new cells, and the values of the arguments are copied into them: 1 and 2. From this moment no link remains betweenaandx, except that they started with the same value.The local variable
savedis created in the same frame and holds a copy ofa.The two assignments swap
aandb. The cells that change are all in the upper frame; those ofmainare not touched.On returning, the frame of
swapValuesdisappears entirely, with the swap it contained.xandyare still 1 and 2.
What the copy protects
Pass by value is not a defect, but the rule that makes functions reasonable. First, the argument can be any expression, not only a variable: swapValues(x + 1, 7) is valid, because the parameter is initialized with a value, wherever it comes from. Second, the caller knows from the declaration, without reading the body, that its variables will not change. Third, inside the function the parameter is one more local variable, which can serve as a working variable without consequences outside.
#include <print>// Number of decimal digits of n.// Precondition: n >= 0.// Postcondition: returns the number of digits of n (1 for 0).int digitCount(int n){ int count{1}; while (n >= 10) { n = n / 10; ++count; } return count;}int main(){ const int population{48'210}; std::println("{} has {} digits", population, digitCount(population));}48210 has 5 digitsThe function destroys its parameter by dividing it by 10 until it has a single digit; since it is a copy, population remains intact, and it can be const. From this follows a reading rule that holds for the whole course: if a parameter does not have the sign & studied in the next section, the function cannot modify the caller’s variable, whatever it does with the parameter.
Loading the content…
Completed
Sign in to save your progress