const
with PointersRecall that const
enables you to inform the compiler that the value of a particular variable should not be modified. Many possibilities exist for using (or not using) const
with function parameters, so how do you choose the most appropriate? Let the principle of least privilege be your guide. Always give a function enough access to the data in its parameters to accomplish its specified task, but no more. This section discusses how to combine const
with pointer declarations to enforce the principle of least privilege.
Chapter 6 explained that when an argument is passed by value, a copy of the argument is passed to the function. If the copy is modified in the called function, the original value in the caller does not change. In some instances, even the copy of the argument’s value should not be altered in the called function.
Consider a function that takes a pointer to the initial element of a built-in array and the array’s size as arguments and subsequently displays the built-in array’s elements. Such a function should loop through the elements and output each individually. The built-in array’s size is used in the function’s body to determine the highest subscript so the loop can terminate when the displaying completes. The size does not need to change in the function body, so it should be declared const
to ensure that it will not change. Because the built-in array is only being displayed, it, too, should be declared const
. This is especially important because built-in arrays are always passed by reference and could easily be changed in the called function. An attempt to modify a const
value is a compilation error.
If a value does not (or should not) change in the body of a function to which it’s passed, the parameter should be declared const
.
Before using a function, check its function prototype to determine the parameters that it can and cannot modify.
There are four ways to pass a pointer to a function:
a nonconstant pointer to nonconstant data,
a nonconstant pointer to constant data (Fig. 8.10),
a constant pointer to nonconstant data (Fig. 8.11) and
a constant pointer to constant data (Fig. 8.12).
Each combination provides a different level of access privilege.
The highest access is granted by a nonconstant pointer to nonconstant data:
the data can be modified through the dereferenced pointer, and
the pointer can be modified to point to other data.
Such a pointer’s declaration (e.g., int*
countPtr
) does not include const
.
A nonconstant pointer to constant data is
a pointer that can be modified to point to any data of the appropriate type, but
the data to which it points cannot be modified through that pointer.
Such a pointer might be used to receive a built-in array argument to a function that should be allowed to read the elements, but not modify them. Any attempt to modify the data in the function results in a compilation error. The declaration for such a pointer places const
to the left of the pointer’s type, as in1
const int* countPtr;
The declaration is read from right to left as “countPtr
is a pointer to an integer constant” or more precisely, “countPtr
is a nonconstant pointer to an integer constant.”
Figure 8.10 demonstrates GNU C++’s compilation error message produced when attempting to compile a function that receives a nonconstant pointer to constant data, then tries to use that pointer to modify the data.
When a function is called with a built-in array as an argument, its contents are effectively passed by reference because the built-in array’s name is implicitly convertible to the address of the built-in array’s first element. However, by default, objects such as array
s and vector
s are passed by value—a copy of the entire object is passed. This requires the execution-time overhead of making a copy of each data item in the object and storing it on the function-call stack. When a pointer to an object is passed, only a copy of the address of the object must be made—the object itself is not copied.
If they do not need to be modified by the called function, pass large objects using pointers to constant data or references to constant data, to obtain the performance benefits of pass-by-reference and avoid the copy overhead of pass-by-value.
Passing large objects using pointers to constant data, or references to constant data, offers the security of pass-by-value.
Use pass-by-value to pass fundamental-type arguments (e.g., int
s, double
s, etc.) to a function unless the caller explicitly requires that the called function be able to directly modify the value in the caller. This is another example of the principle of least privilege.
A constant pointer to nonconstant data is a pointer that
always points to the same memory location, and
the data at that location can be modified through the pointer.
Pointers that are declared const
must be initialized when they’re declared, but if the pointer is a function parameter, it’s initialized with the pointer that’s passed to the function.
Figure 8.11 attempts to modify a constant pointer. Line 9 declares pointer ptr
to be of type int*
const
. The declaration is read from right to left as “ptr
is a constant pointer to a nonconstant integer.” The pointer is initialized with the address of integer variable x
. Line 12 attempts to assign the address of y
to ptr
, but the compiler generates an error message. No error occurs when line 11 assigns the value 7
to *ptr
—the nonconstant value to which ptr
points can be modified using the dereferenced ptr
, even though ptr
itself has been declared const
.
The minimum access privilege is granted by a constant pointer to constant data:
such a pointer always points to the same memory location, and
the data at that location cannot be modified via the pointer.
This is how a built-in array should be passed to a function that only reads from the array, using array subscript notation, and does not modify it. The program of Fig. 8.12 declares pointer variable ptr
to be of type const
int*
const
(line 12). This declaration is read from right to left as “ptr
is a constant pointer to an integer constant.” The figure shows the Xcode LLVM compiler’s error messages that are generated when an attempt is made to modify the data to which ptr
points (line 16) and when an attempt is made to modify the address stored in the pointer variable (line 17)—these show up on the lines of code with the errors in the Xcode text editor. In line 14, no errors occur when the program attempts to dereference ptr
, or when the program attempts to output the value to which ptr
points, because neither the pointer nor the data it points to is being modified in this statement.