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

Text Box: int main()
{ Date D1(10, 5, 2005);
  cout << D1+=5;
}

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...

Text Box: // Add a specific number of days to a date
const Date &Date::operator+=( int additionalDays )
{
   for ( int i = 0; i < additionalDays; i++ )
      helpIncrement();
   return *this;    // enables cascading
}
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:

Text Box: // Overloaded output operator for class Array 
ostream &operator<<( ostream &output, const Array &a )
{
   int i;
   output << '\t';
   for ( i = 0; i < a.getSize(); i++ ) {
      output << setw( 12 ) << a[ i ];
      if ( ( i + 1 ) % 4 == 0 ) // 4 numbers per row of output
         output << endl;
   }
   if ( i % 4 != 0 )
      output << endl;
   return output;   // enables cout << x << y;
}

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.

 

Text Box: // Get the size of the array
int Array::getSize() const 
{ return size; 
}
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.