In C++, we see the unusual situation of reference
parameters qualified as const and function (return) types that are by reference
or constant reference. First, several questions need to be answered:
1.
Why
pass an object as a constant reference?
Although they aren’t large in this example,
objects can become very large. Common practice is to eschew pass by value due
to the overhead required to copy the object to the parameter, and instead pass
the object’s address while not permitting any change to the object.
2.
Why
would a function return a reference or a constant reference?
Suppose an array’s elements
are objects of some class’ type. If a function takes the array and returns an
element of that array, if the function’s (return) type isn’t a reference to the
element, a copy of that element is returned, and there will be no way to access
the actual array element. On the other hand, a constant reference is returned
when only access is required while we are still concerned with copying
something large.
3.
Why
would a function be qualified as constant by placing the keyword const after
its parameter list?
Any member function that isn’t a mutator can
(and should) be qualified as const. This qualifier assures the compiler that
this function will not change any data members of the object. But, why? Suppose
an object is passed to a function as a constant reference. Any invocation of a
member function of that object can’t mutate it, since the reference is
constant. The only way such a function call can be compiled is to assure the
compiler it won’t mutate the object, which is accomplished by qualifying it as
const. Note that this situation is only found with member functions of a class.
Let’s try to clarify this using several examples.
First, let’s look at why we might use a const
reference function (return) type. In the Date class with operator
overloading, we have:
const Date &operator+=( int ); // add
days, modify object
and suppose we take the unlikely but possible usage
as shown in the box. The output will be October 10, 2005, but how does it get placed
into the stream? Let’s have a look at the code of the operator, below, with the
code of interest bold...
Note that it returns the object, a Date, which matches the function’s
(return) type. And, since the type is followed by the & for a reference, it
means that the Date object D1 itself is provided to the stream in the code
above, not a copy. This is done to save
overhead; a copy of the object isn’t required for this operation. Further, the
const matches the convention for stream insertion, which is to pass a constant
reference to an object, since << doesn’t mutate objects.
But, wait! There is a dangerous aspect to functions
whose (return) type is a reference to something, and that is the possibility
that the item returned won’t exist when the function terminates. In practice,
the only place where reference function (return) types are found is as member
functions of classes. It is very important to note that the item returned by
one of this special class of functions must have class scope, i.e. be visible
throughout the class.
Next, an example where an object is passed as a
constant reference, and its members are accessed in the called function,
requiring const qualifiers on its member functions.
In the Array class, the << operator
need not be a friend of the class, because it can access data members via
member functions in the class’ interface (public area). This function can be
(and is) protyped in Array.h, but below (not inside of) the class declaration.
The implementation is in the box:
Note the call to the getSize() member
function of the Array object a. Since a is const, it can’t be
changed, meaning of course that its data members must remain as they are.
While it seems that it would not be necessary, and that an attempt to
alter a data member of a constant object would be handled implicitly, most C++
compilers (including g++) require explicit assurance that a member function can
not alter any data member by qualifying it with the keyword const.
The implementation of getSize() is in the
box. Qualifying it as const is accomplished by following the parameter list
(which is empty) with the keyword const. Without the keyword const,
you will get an error on the call of getSize() in the << operator that it
discards qualifiers. In other words, the call of getSize() is to a
member function that is not const, and therefore the Array object is not
guaranteed to be unaltered by the call.
Let’s summarize the main points covered, and add
info where appropriate:
Ø When a function (return)
type is followed by &, the function will return a reference to something,
not a copy. But, beware: the item returned must have a lifetime outside the
scope of the called function. That’s why this special situation is almost
always found with member functions of an object type that return *this,
the object itself, or one of the data members. If the function’s type is
qualified as const, then the caller may only access const members with the
object returned, or may not try to alter the variable returned.
Ø When a parameter of a
function X is prefaced by const, we know what it means if the parameter is a
simple type. But, if the parameter is an object Z, then any member function of
Z called in X must be qualified as const, since Z is itself const. Otherwise,
the compiler will report an error regarding discards qualifiers.
Ø We see that some functions
in classes are qualified as constant, by having the keyword const
appended to their headers. In this situation, the function may not alter any
data members of the class.