6.6. Functions and the const Keyword

Time: 00:05:55 | Download: Large, Large (CC), Small | Streaming, Streaming (CC) | Slides (PDF)
Review

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 in the function only affect 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:

table(payment, principal, R, N);
(a)
void table(double payment, double balance, double R, int N)
{
	double	total_interest = 0;
	double	total_principal = 0;

	for (int i = 1; i <= N; i++)
	{
		double	to_interest = balance * R;
		double	to_principal = payment - to_interest;
		total_interest += to_interest;
		total_principal += to_principal;

		balance -= (payment - to_interest);

		cout << setw(3) << i << ": " << setw(10) << balance <<
			setw(10) << to_interest << setw(10) << to_principal <<endl;
	}

	cout << endl << setw(25) << total_interest << setw(10) <<
		total_principal << endl;
}
(b)
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. In this example, the function modifies the balance while the principal remains unchanged.
  1. Once entered, the principal never changes - it always represents the original amount borrowed. The function call passes it by value to the table function's balance parameter.
  2. When the program calls the table function, the balance is the same as the principle, but the two amounts become different with the first payment, and the function updates it each time the borrower makes loan payment.

However, there is a potential disadvantage of pass-by-value. If the data passed to the function is large, the system requires additional time to copy it. Pass-by-pointer and pass-by-reference solve the copying problem by not copying the data and maintaining a single copy of the data in the caller's scope. They create an alias for the data in the function's scope by passing a pointer or reference to one of the function's parameters. However, creating an alias eliminates the "protection" that prevents the function from modifying the original data. Is there some way to enjoy the advantage of a fast-passing technique while maintaining the protection? Applying the const keyword to a function parameter prevents the function from modifying it.

const Reference Parameters

Used with a parameter, the const keyword forms a promise or guarantee that the function will not change its parameter and, therefore, it won't change the caller's argument. If the function attempts to change the parameter, the compiler halts the compilation process, flagging the attempt as a syntax error.

struct ReallyBig
{
	char	code;
	double	cost;
		. . .
};
ReallyBig	large;
		.
		.
		.
		.
function(large);
void function(const ReallyBig& big)
{
	double tax = big.cost * 0.077;
	//big.code = 'Z';
		. . .
}
(a)(b)(c)
Pass-by-reference with a const parameter.
  1. Large amounts of data take longer to pass-by-value (i.e., pass by copy).
  2. Defining and passing a large structure.
  3. The execution time of pass-by-reference is independent of the data's size. Any attempt to change a const parameter is a compile-time error, making it a one-way or input-only passing method.

const Pointer Parameters

Pass-by-pointer is another efficient way of passing large data items to functions. Like pass-by-reference, the const keyword prevents a function from modifying the data (i.e., the parameters). However, using const with pointer arguments requires a little more thought than it did with references because a pointer subtly requires two related but distinct variables: the pointer storing the address of the data and the data itself. Programmers can make either or both variables constant.

An abstract representation, consisting of two rectangles, of the variables implementing pass-by-pointer. One rectangle represents the pointer variable, p, and another represents an object named widget, an instance of the ReallyBig structure. An arrow representing a pointer runs from p to widget. The main function creates a widget and passes it to both functions.
void function1(const ReallyBig* p);
void function2(ReallyBig* const p);

int main()
{
	ReallyBig widget = { 'x', 19.95, . . . };

	function1(&widget);
	function2(&widget);

	return 0;
}
(a)(b)
void function1(const ReallyBig* p)
{
	//p->cost = 29.95;	// error
	p = new ReallyBig;
}
void function2(ReallyBig* const p)
{
	p->cost = 29.95;
	//p = new ReallyBig;		// error
}
(c)(d)
Pass-by-pointer with a const parameter. Pass-by-pointer requires two variables, and programmers can make each one constant. const binds or applies to the expression to its right, allowing programmers to control what the function holds constant.
  1. The relationship between the function pointer argument, p, and the data, an instance of the ReallyBig structure named widget.
  2. When used, the const keyword must appear in both the function definitions and prototypes.
  3. In this position, const operates on the data, preventing the function from modifying the data (i.e., the structure's fields). However, the function can change the address saved in the pointer p, but the change is not propagated back to the function call because p is a local variable. Programmers can equivalently express the parameter as ReallyBig  const* p.
  4. In this position, const operates on the pointer, preventing the function from changing the address saved in p. However, the program can change the data, allowing data to flow into and out of the function.
Programmers can make both the pointer and the data constant by using const in both locations: void function3(const ReallyBig* const p).

Returning Constant Data

Data a function returns by pointer or reference is also subject to change. This condition is beneficial sometimes but becomes increasingly problematic when the returned data is an array (the next chapter), C-string (Chapter 8), or a class member (Chapter 9). C++ also resolves these problems with the const keyword.

Returning const References

const ReallyBig& function1()
{
	static ReallyBig widget = { 'x', 19.95, ... };
	return widget;
}
const ReallyBig& r1 = function1();

//r1.cost = 29.95;	// error
 
 
(a)(b)
Returning const references from a function. A program can change the contents of a static function-scope variable returned by reference. The change can cause errors if the function later relies on the previous value or is called from a different location and should return the original value. Programmers can make the function return value constant, preventing an external change.
  1. The function returns a const reference
  2. The const keyword is necessary to make the data types on the left- and left-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

const ReallyBig* function2()
{
	static ReallyBig widget = { 'x', 19.95, ... };
	return &widget;
}
const ReallyBig* p1 = function2();

//p1->cost = 29.95;		// error

p1 = new ReallyBig;		// okay
(a)(b)
ReallyBig* const function3()
{
	static ReallyBig widget = { 'x', 19.95, ... };
	return &widget;
}
ReallyBig* const p2 = function3();

p2->cost = 29.95;		// okay

//p2 = new ReallyBig;		// error
(c)(d)
Returning const pointers from a function. Pointers returned by a function consist of the same two components as those passed as arguments: an address and the date. Programmers can "protect" either or both with const, depending on where they place the keyword. The variables saving the returned values are also const, and the placement of the keyword is in the same relative location as in the function headers.
  1. Returns a const pointer preventing changes to the data
  2. The const keyword prevents the program changing the data
  3. Returns a const pointer preventing changing the address stored in the pointer variable
  4. The const keyword prevents changing the address stored in p2

Constant Member Functions

object.member()
void member() const;
void member() const { . . . }
(a)(b)(c)
Constant member functions. Classes typically "hide" or protect their data members by making them private. Nevertheless, sometimes clients need to access the data, and classes can prevent it from making improper changes with the const keyword. Chapters 9 and 10 provide greater detail and authentic examples.
  1. object is an instance of a class with a member function named member.
  2. Programmers place the const keyword between the parameter list and the terminating semicolon in a function prototype.
  3. They place const between the parameter list and the function body in a function definition.