Passing data to a function by value provides some insulation between the client (the caller) and the supplier (the function itself). Specifically, any changes made to the data within the function affect only the function's copy. For example, suppose that we want to rewrite the mortgage.cpp example from Chapter 3. The new version moves the code calculating and printing the amortization table to a function called from the program's main:
Pass-by-value "protects" function arguments.
In the context of borrowing and repaying money, the principal is the borrowed amount, and the balance is the amount the borrower has not repaid. Initially, the two values are the same, but the balance changes whenever the borrower makes a payment, and, as a matter of record, the principal is fixed when the loan is made. The function implements this relationship with pass-by-value and is inherently one-way, making it an IN-only parameter.
The function call passes a copy of the variable, principal, to the function's balance parameter.
Pass-by-value also allows programs to pass the results of complex expressions as function arguments.
During each iteration of the for-loop, the function updates the value stored in balance. However, changes made to balance are not propagated back to principle.
A pass-by-value drawback.
In some cases, pass-by-value has a disadvantage compared to the other passing mechanisms. Aside from some small size-independent overhead, the time required to copy data from an argument to a parameter is proportional to the argument's size. Consequently, if the data passed to a function is large, the system takes longer to copy it. Imagine an extended structure, ReallyBig, that contains numerous fields. Passing objects instantiated from it (e.g., widget) takes more time than passing a few fundamental arguments. Whether the additional time is significant depends on how often the client program calls it and what operations await its completion.
Large amounts of data take longer to pass-by-value (i.e., pass by copy).
Defining and passing a large structure.
Pass-by-pointer and pass-by-reference solve this problem by not copying the data. They maintain a single instance of the data in the caller's scope and create an alias for it in the function's scope. However, creating an alias eliminates the "protection" that prevents the function from modifying the original data. Applying the const keyword to a function parameter prevents the function from modifying it, even when passed by pointer or by reference, maintaining the protection.
Used with a reference parameter, the const keyword prevents a function from changing it. Consequently, the function can't change the caller's argument. Attempting to change a const reference parameter is a compile-time error - the compiler halts the compilation process, flagging the attempt as a syntax error.
Pass-by-reference with a const parameter.
The execution time of pass-by-reference is independent of the data's size, allowing it to pass large amounts to functions efficiently. Unmodified, it implements an INOUT parameter, allowing data to flow into and out of a function. Adding the const keyword to a parameter effectively changes it from an INOUT parameter to an IN-only parameter.
The const keyword is necessary in both the function prototype (declaration) and definition.
It is a compile-time error for a function to attempt to change a value referenced by a const parameter.
Pass-by-pointer is another efficient way of passing large amounts of data to a function. Like pass-by-reference, the const keyword prevents a function from modifying the data (i.e., the parameters). However, using const with pointer arguments is more complicated because it involves two variables: a pointer storing the address of the data and the data itself. Programmers can make either or both variables constant.
Pass-by-pointer with a const parameter.
Pass-by-pointer involves two variables, and the const keyword affects them individually. const is "greedy," using all the variable definition on its right side. Recognizing this behavior allows programmers to control what the function holds constant. Carefully placing const results in two patterns, one with two variations, controlling which part of the pointer system is constant. The important feature is the relative position of the asterisk and const.
The pointer system consists of the function parameter, p, and an instance of the widget structure named ReallyBig.
const must appear in both the function definitions and the prototypes (declarations). function1 illustrates that programmers can place const in two locations with equivalent results. While only one is necessary, C++ allows multiple equivalent declarations, so the two prototypes do not conflict. As the three function calls demonstrate, how programs call the functions is independent of which parts are held constant.
When placed before the asterisk, the compiler applies const to the data - the widget object in this example. Consequently, the function cannot modify the data (i.e., change any of the structure's fields), causing a compile-time error if it attempts to do so. However, the function can change the address stored in the pointer p, but the change is not propagated back to the function call because p is a local variable.
Placed after the asterisk, the compiler applies const only to the parameter. This interpretation prevents the function from changing the address stored in p, but allows it to change the data (object) that p points to.
Adding const in both locations makes both the pointer and the data constant, achieving the same "protection" as pass-by-value, while retaining the passing efficiency of pass-by-pointer.
Returning Constant Data
function, const
Data that a function returns by pointer or reference is also subject to change. Object-oriented programming and operator overloading, introduced in subsequent chapters, will make great use of this. However, there are situations where this changeability is more problematic than beneficial. For example, when returning an array (Chapter 7) or when a getter function returns a class member (Chapter 9). C++ also resolves these problems with the const keyword. The following examples primarily focus on syntax, leaving the details to the subsequent chapters.
Returning const references from a function.
Without modification, a program can change the contents of a static function-scope variable returned by reference. Depending on the specific problem the program solves, this changeability may be beneficial or not. Programmers can make the function's return value const, preventing external changes.
The function returns a const reference
The const keyword is necessary to make the data types on the left- and right-hand sides of the assignment operator the same - a property called assignment compatibility. Any attempt to change the data is a compile-time error
Returning const pointers from a function.
Like pointer parameters, the pointer a function returns consists of an address and the data it points to. Consequently, programmers can "protect" either or both with const, depending on where they place the keyword. To maintain assignment compatibility, the variables saving the returned pointer must also be const, and the placement of the keyword is in the same relative location as in the function headers.
Returns a const pointer that prevents the program from changing the data.
Any attempt to change the data is a compile-time error.
Returns a const pointer that prevents the program from changing the address stored in the pointer variable.
Any attempt to change the address is a compile-time error.
Constant Member Functions
function, const, member function, member variable, getter functions
Variable Declaration
Function Prototype
Function Definition
T* foo;
T* member() const;
T* member() const { return foo; }
Constant member functions.
Objects typically "hide" their data members by making them private, preventing other objects from "seeing" or changing them. Nevertheless, sometimes those objects have a legitimate need to "see" the stored data but not to change it. If the data member doesn't contain any pointers, member functions, called getters, can implement one-way data viewing with return-by-value. If the data does contain a pointer, getters can "protect" it by returning a const pointer. The example assumes:
An object with a private pointer member variable named foo.
The data type T is unspecified but is typically an object instantiated from a structure or class.
The member function named member() is a getter that returns foo.
Chapters 9 and 10 provide greater detail and authentic examples.