I l@ve RuBoard Previous Section Next Section

4.3 What Are mutable and const?

Consider the following small function:



int sum( const Triangular &trian ) 


{ 


   int beg_pos = trian.beg_pos(); 


   int length  = trian.length(); 


   int sum = 0; 


   for ( int ix = 0; ix < length; ++ix ) 


         sum += trian.elem( beg_pos+ix ); 


   return sum; 


} 

trian is a const reference parameter. The compiler, therefore, must guarantee that trian is not modified within sum(). Potentially, trian is modified within any member function that it invokes. To be certain that trian is not modified, the compiler must be sure that beg_pos(), length(), and elem() do not change the class object that invokes them. How does the compiler know that? The class designer must tell the compiler by labeling as const each member function that does not modify the class object:



class Triangular { 


public: 


   // const member functions 


   int length()        const { return _length;  } 


   int beg_pos()       const { return _beg_pos; } 


   int elem( int pos ) const; 





   // non-const member functions 


   bool next( int &val ); 


   void next_reset() { _next = _beg_pos - 1; } 





    // ... 


private: 


   int _length; // number of elements 


   int _beg_pos; // beginning position of range 


   int _next;    // next element to iterate over 





   // static data members are covered in Section 4.5 


   static vector<int> _elems; 


}; 

The const modifier follows the parameter list of the function. A const member function defined outside the class body must specify the const modifier in both its declaration and definition. For example,



int Triangular::elem( int pos ) const 


    { return _elems[ pos-1 ]; } 

Although the compiler does not analyze each function to determine whether it is a const or a non-const member function, it does examine each const member function to make sure that it doesn't modify the class object. For example, if we were to declare the following instance of next() as a const member function, that declaration would be flagged as an error because it clearly modifies the class object that invokes it.



bool Triangular::next( int &value ) const 


{ 


   if ( _next < _beg_pos + _length - 1 ) 


   { 


        // error: modifying _next 


        value = _elems[ _next++ ]; 


        return true; 


   } 


   return false; 


} 

In the following class, the val() member function does not directly modify the _val data member, but it does return a non-const reference to _val. Can val() still be declared as const?



class val_class { 


public: 


   val_class( const BigClass &v ) 


       : _val( v ){} 





   // is this ok? 


   BigClass& val() const { return _val; } 





private: 


   BigClass _val; 


}; 

No. Returning a non-const reference to _val in effect opens it to modification in the general program. Because a member function can be overloaded based on its constness, one solution to this problem is to provide two definitions ?a const and a non-const version ?such as the following:



class val_class { 


public: 


   const BigClass& val() const { return _val; } 


   BigClass& val(){ return _val; } 


   // ... 


}; 

For a non-const class object, the non-const instance of val() is invoked. For a const class object, the const instance is invoked. For example,



void example( const BigClass *pbc, BigClass &rbc ) 


{ 


    pbc->val(); // invokes const instance 


    rbc.val();    // invokes non-const instance 


    // ... 


} 

When you design a class, it is important to identify the const member functions. If you forget, each const reference class parameter will be unable to invoke the non-const portion of the class interface. Users may become abusive. Retrofitting const onto a class is challenging, particularly if there is widespread invocation of one member function by another.

Mutable Data Member

Here is an alternative implementation of sum() using the next() and next_reset() member functions to iterate across the elements of trian.



int sum( const Triangular &trian ) 


{ 


     if ( ! trian.length() ) 


          return 0; 





     int val, sum = 0; 


     trian.next_reset(); 


     while ( trian.next( val )) 


             sum += val; 





     return sum; 


} 

Will this code compile? No, at least not yet. trian is a const class object. next_reset() and next() are not const member functions because both functions modify _next. Their invocation by trian, therefore, is flagged as an error.

If we are to use this implementation of sum(), both next() and next_reset() must become const member functions. We can do that by making a fine distinction.

_length and _beg_pos support the attributes of the numeric series abstraction. If we change the length or beginning position of trian, in a sense we change its identity. It is no longer equal to what it was before the change. _next, however, supports our implementation of an iterator mechanism; it does not itself support the numeric sequence abstraction. Changing the value of _next does not semantically change the class object and, one might claim, does not violate the const-ness of the object. (I said we would be making a fine distinction.) The mutable keyword provides us with a way of making just such a claim. By identifying _next as mutable, we are saying that changes to it do not violate the const-ness of our class object.



class Triangular { 


public: 


   bool next( int &val ) const; 


   void next_reset() const { _next = _beg_pos - 1; } 


   // ... 





private: 


   mutable int _next, 


   int _beg_pos; 


   int _length; 


}; 

next() and next_reset() can now modify _next and still be declared as const member functions, allowing our alternative implementation of sum(). Here is a small program that exercises sum() on three Triangular class objects:



int main() 


{ 


      Triangular tri( 4 ); 


      cout << tri << " -- sum of elements: " 


           << sum( tri ) << endl; 





      Triangular tri2( 4, 3 ); 


      cout << tri2 << " -- sum of elements: " 


           << sum( tri2 ) << endl; 





      Triangular tri3( 4, 8 ); 


      cout << tri3  << " -- sum of elements: " 


           << sum( tri3 ) << endl; 





} 

When compiled and executed, it generates the following output:



( 1 , 4 ) 1 3 6 10  -- sum of elements: 20 


( 3 , 4 ) 6 10 15 21  -- sum of elements: 52 


( 8 , 4 ) 36 45 55 66  -- sum of elements: 202 
    I l@ve RuBoard Previous Section Next Section