6.14. References and Reference
Parameters
Two ways to pass arguments to
functions in many programming languages are pass-by-value and pass-by-reference. When
an argument is passed by value, a copy of the argument's value is made and passed (on the function
call stack) to the called function. Changes to the copy do not affect the
original variable's value in the caller. This prevents the accidental side
effects that so greatly hinder the development of correct and reliable software
systems. Each argument that has been passed in the programs in this chapter so
far has been passed by value.
Performance Tip 6.5
|
One disadvantage
of pass-by-value is that, if a large data item is being passed, copying that
data can take a considerable amount of execution time and memory
space. |
Reference Parameters
This section introduces reference
parameters—the first of the two means C++
provides for performing pass-by-reference. With pass-by-reference, the caller
gives the called function the ability to access the caller's data directly, and
to modify that data if the called function chooses to do so.
Performance Tip 6.6
|
Pass-by-reference is good for performance reasons, because
it can eliminate the pass-by-value overhead of copying large amounts of
data. |
Software Engineering Observation 6.11
|
Pass-by-reference can weaken security, because the called
function can corrupt the caller's
data. |
Later, we'll show how to achieve
the performance advantage of pass-by-reference while simultaneously achieving
the software engineering advantage of protecting the caller's data from
corruption.
A reference parameter is an alias for its
corresponding argument in a function call. To indicate that a function parameter
is passed by reference, simply follow the parameter's type in the function
prototype by an ampersand (&); use the same
convention when listing the parameter's type in the function header. For
example, the following declaration in a function header
when read from right to left is pronounced "count is a reference to an int." In the function call, simply mention the variable by
name to pass it by reference. Then, mentioning the variable by its parameter
name in the body of the called function actually refers to the original variable
in the calling function, and the original variable can be modified directly by
the called function. As always, the function prototype and header must
agree.
Passing Arguments by Value and by
Reference
Figure
6.18 compares pass-by-value and
pass-by-reference with reference parameters. The "styles" of the arguments in
the calls to function squareByValue and function
squareByReference are identical—both
variables are simply mentioned by name in the function calls. Without checking
the function prototypes or function definitions, it is not possible to tell from
the calls alone whether either function can modify its arguments. Because
function prototypes are mandatory, the compiler has no trouble resolving the
ambiguity.
Fig. 6.18. Passing arguments by value and by
reference.
1 // Fig. 6.18: fig06_18.cpp
2 // Comparing pass-by-value and pass-by-reference with references.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 int squareByValue( int ); // function prototype (value pass)
8 void squareByReference( int & ); // function prototype (reference pass)
9
10 int main()
11 {
12 int x = 2; // value to square using squareByValue
13 int z = 4; // value to square using squareByReference
14
15 // demonstrate squareByValue
16 cout << "x = " << x << " before squareByValue\n";
17 cout << "Value returned by squareByValue: "
18 << squareByValue( x ) << endl;
19 cout << "x = " << x << " after squareByValue\n" << endl;
20
21 // demonstrate squareByReference
22 cout << "z = " << z << " before squareByReference" << endl;
23 squareByReference( z );
24 cout << "z = " << z << " after squareByReference" << endl;
25 return 0; // indicates successful termination
26 } // end main
27
28 // squareByValue multiplies number by itself, stores the
29 // result in number and returns the new value of number
30 int squareByValue( int number )
31 {
32 return number *= number; // caller's argument not modified
33 } // end function squareByValue
34
35 // squareByReference multiplies numberRef by itself and stores the result
36 // in the variable to which numberRef refers in function main
37 void squareByReference( int &numberRef )
38 {
39 numberRef *= numberRef; // caller's argument modified
40 } // end function squareByReference
|
x = 2 before squareByValue
Value returned by squareByValue: 4
x = 2 after squareByValue
z = 4 before squareByReference
z = 16 after squareByReference
|
Common Programming Error 6.14
|
Because
reference parameters are mentioned only by name in the body of the called
function, you might inadvertently treat reference parameters as pass-by-value
parameters. This can cause unexpected side effects if the original copies of the
variables are changed by the
function. |
Chapter
8 discusses pointers; pointers enable an
alternate form of pass-by-reference in which the style of the call clearly
indicates pass-by-reference (and the potential for modifying the caller's
arguments).
Performance Tip 6.7
|
For
passing large objects, use a constant reference parameter to simulate the
appearance and security of pass-by-value and avoid the overhead of passing a
copy of the large object. |
Software Engineering
Observation 6.12
|
Many
programmers do not bother to declare parameters passed by value as
const, even though the called function
should not be modifying the passed argument. Keyword const in this context would protect only a copy of the
original argument, not the original argument itself, which when passed by value
is safe from modification by the called
function. |
To specify a reference to a
constant, place the const qualifier before the type specifier in the
parameter declaration.
Note the placement of & in function
squareByReference's parameter list (line 37, Fig. 6.18). Some C++ programmers
prefer to write the equivalent form int& numberRef.
Software Engineering Observation 6.13
|
For the
combined reasons of clarity and performance, many C++ programmers prefer that
modifiable arguments be passed to functions by using pointers (which we study in
Chapter
8), small nonmodifiable arguments be passed by value
and large nonmodifiable arguments be passed to functions by using references to
constants. |
References as Aliases within a
Function
References can also be used as aliases
for other variables within a function (although they typically are used with
functions as shown in Fig. 6.18). For example, the code
int count = 1; // declare integer variable count
int &cRef = count; // create cRef as an alias for count
cRef++; // increment count (using its alias cRef)
increments variable count by
using its alias cRef. Reference variables must be initialized in their
declarations (see Fig.
6.19 and Fig.
6.20) and cannot be reassigned as aliases to
other variables. Once a reference is declared as an alias for another variable,
all operations supposedly performed on the alias (i.e., the reference) are
actually performed on the original variable. The alias is simply another name
for the original variable. Taking the address of a reference and comparing
references do not cause syntax errors; rather, each operation actually occurs on
the variable for which the reference is an alias. Unless it is a reference to a
constant, a reference argument must be an lvalue (e.g., a variable
name), not a constant or expression that returns an rvalue (e.g., the result of
a calculation). See Section
5.9 for definitions of the terms lvalue and rvalue.
Fig. 6.19. Initializing and using
a reference.
1 // Fig. 6.19: fig06_19.cpp
2 // References must be initialized.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 int main()
8 {
9 int x = 3;
10 int &y = x; // y refers to (is an alias for) x
11
12 cout << "x = " << x << endl << "y = " << y << endl;
13 y = 7; // actually modifies x
14 cout << "x = " << x << endl << "y = " << y << endl;
15 return 0; // indicates successful termination
16 } // end main
|
|
Fig. 6.20. Uninitialized
reference causes a syntax error.
1 // Fig. 6.20: fig06_20.cpp
2 // References must be initialized.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 int main()
8 {
9 int x = 3;
10 int &y; // Error: y must be initialized
11
12 cout << "x = " << x << endl << "y = " << y << endl;
13 y = 7;
14 cout << "x = " << x << endl << "y = " << y << endl;
15 return 0; // indicates successful termination
16 } // end main
|
| Borland C++ command-line compiler
error message: |
Error E2304 C:\cppfp_examples\ch06\Fig06_20\fig06_20.cpp 10:
Reference variable 'y' must be initialized in function main()
|
| Microsoft Visual C++ compiler
error message: |
C:\cppfp_examples\ch06\Fig06_20\fig06_20.cpp(10) : error C2530: 'y' :
references must be initialized
|
| GNU C++ compiler error
message: |
fig06_20.cpp:10: error: 'y' declared as a reference but not initialized
|
Returning a Reference from a
Function
Functions can return references,
but this can be dangerous. When returning a reference to a variable declared in
the called function, the variable should be declared static within that function. Otherwise, the reference
refers to an automatic variable that is discarded when the function terminates;
such a variable is said to be "undefined," and the program's behavior is
unpredictable. References to undefined variables are called dangling references.
Common Programming Error 6.15
|
Not
initializing a reference variable when it is declared is a compilation error,
unless the declaration is part of a function's parameter list. Reference
parameters are initialized when the function in which they are declared is
called. |
Common Programming Error 6.16
|
Attempting to
reassign a previously declared reference to be an alias to another variable is a
logic error. The value of the other variable is simply assigned to the variable
for which the reference is already an
alias. |
Common Programming Error
6.17
|
Returning a
reference to an automatic variable in a called function is a logic error. Some
compilers issue a warning when this
occurs. |
Error Messages for Uninitialized
References
The C++ standard does not specify the
error messages that compilers use to indicate particular errors. For this
reason, Fig. 6.20 shows the error messages produced by the Borland C++
command-line compiler, Microsoft Visual C++ compiler and GNU C++ compiler when a
reference is not initialized.