The C++ implementation of pass-by-reference is without parallel in Java, making this section especially important for programmers transitioning from Java to C++. Paradoxically, it's easy for programmers to use pass-by-reference but relatively more difficult to understand it. The following figures illustrate the pass-by-reference syntax and behavior, but three preliminary observations facilitate the discussion and clarify the examples.
C++ only allows variables as pass-by-reference function arguments. This assertion is an incomplete oversimplification, but it represents the most frequent situation and is the only case considered in the text.
The compiler maps variable and function names to memory addresses1. Therefore, whenever a program calls or jumps to a function, it executes the machine instructions at the named memory location.
C++ implements pass-by-reference by temporarily mapping two variable names to the same memory address or location. The first variable is the argument in the function call, and the second is the parameter in the function header. Although both names refer to the same variable or memory location, the program defines names in different scopes.
Clarifying Terminology
Confusingly, the meaning of the term pass-by-reference varies from one programming language to another. In compiler theory and most programming languages, pass-by-reference describes the passing technique we called "pass-by-pointer" in the previous section.
Fischer and LeBlanc2 state, "Reference parameters represent the address of an actual parameter" (1988, p. 485). The Edpresso Team3 explicitly joins the two terms: "Pass by reference (also called pass by address) means to pass the reference of an argument in the calling function to the corresponding formal parameter of the called function so that a copy of the address of the actual parameter is made in memory..." Their statement suggests that a "reference" is an address, which explains the terms "dereference operator" and "dereferencing a variable."
Alternatively, IBM4 titled one of its z/OS documentation pages "Pass by reference (C++ only)," suggesting that the documented concepts and examples apply only to C++. (I highly recommend studying this short page.) The following description of pass-by-reference applies only to C++ and represents the meaning used throughout this textbook.
Observing the same procedure as the previous sections, the following figures demonstrate pass-by-reference by comparing a simple program with its effects on memory, articulating key operations with specific memory changes. The address of a, 0x12, is arbitrary and plays no significant role in the program. In these examples, the compiler simply maps the parameter p to the same memory address as the argument a - p is, in the truest sense, an alias for a while the functions are active.
Passing Simple Data By Reference
As in the two previous sections, simple data are any of the fundamental, built-in data types like char, int, or double. This example uses data of type int, but the behavior is the same for all fundamental types.
void func(int& p);
int main()
{
int a = 5; // step 1
func(a); // step 2
}
void func(int& p)
{
p = p + 1; // step 3
} // step 4
Pass by reference with simple data.
The variable a is defined and initialized to 5
Function func is called and while it is active, p refers to or labels the same memory location as a
The function increments the value located at p, which is exactly the same as incrementing variable a since the variables map to the same memory location
The function ends, the variable p goes out of scope, but the value in variable a is changed from 5 to 6
Passing Structured Data By Reference
The structured data is an object - an instance of a structure or class.
struct part
{ char type;
int id;
};
void func(part& p);
int main()
{
part a = { 'd', 10 }; // step 1
func(a); // step 2
}
void func(part& p)
{
p.id = 1000; // step 3
} // step 4
Pass by reference with structured data.
The fields of variable a are defined and initialized to 'd' and 10 respectively
The program calls function func and while it's active, the name p refers to the same memory location as a
The id field in structure p is modified, assigning 1000 to it, but a and p refer to the same memory location, so any change to p is also a change to a
The function ends and the value in the id field of a is changed from 10 to 1000
Pass-By-Reference: Advantages and Disadvantages
Superficially, pass-by-reference and by-pointer seem quite similar. Specifically, both passing methods allow passing data into and out of a function through the function call arguments. Consequently, they share some advantages and disadvantages, but the fundamental difference in their respective implementations guarantees they have unique benefits and drawbacks.
void func(part& p)
{
p = new part { 'd', 1000 };
}
. . .
part p = { 'd', 10 };
func(p);
cout << "Output: " << p.id << endl;
void func(part* p)
{
p = new part { 'd', 1000 };
}
. . .
part p = { 'd', 10 };
func(&p);
cout << "Output: " << p.id << endl;
Output: 1000
Output: 10
(e)
(f)
Programming examples: references vs. pointers. C++ overloads the & operator to denote reference variables. The compiler distinguishes between a reference, the address-of operator, and the logical and bitwise AND operators by the operator's context (where the program uses it).
Used in a variable definition, the & creates a reference variable and C++ requires programs to initialize them in the defining statement. The & appears on the left-hand side of the assignment operator.
The address-of operator, in conjunction with a variable name, forms an expression that is the variable's address. The & appears on the right-hand side of the assignment operator.
Programmers use the dot operator to access the fields or members of reference objects. Functions propagate changes made through reference variables back to the call.
Programmers dereference and select object fields with the arrow operator. Calls must pass an address. Functions propagate changes made through pointer variables back to the call.
C++ allows a program to return a new object through a reference parameter.
C++ cannot return a new object through a pointer parameter.
Referring to The complete C++ compilation process, the compiler component processes the individual source code (i.e., .cpp) files one at a time, producing object code files. The linker or loader joins the individual object files, library functions, and a system runtime file, creating an executable program. Matching the addresses of functions defined in one source code file to function calls made in different files is a primary task of the linker or loader. Consequently, functions can pass data by reference within a single executable program but not between different programs.
Fischer, C. N., & LeBlanc, R. J., Jr. (1988). Crafting a compiler. Menlo Park, CA: The Benjamin/Cummings Publishing Company, Inc.
Although not covered in introductory courses, it is possible to have systems of separate programs running concurrently, sharing data, and cooperating to solve a problem. If the programs run on the same computer, they can share data through addresses or pointers. For example, the operating system is nothing more than a program, exchanging data with our programs through system calls using pass-by-value and pointer. Other examples include programs interfacing with database management systems or other server programs.