Ensuring That a Member Function Doesnt Modify Its Object

Ensuring That a Member Function Doesn t Modify Its Object

Problem

You need to invoke member functions on a const object, but your compiler is complaining that it can't convert the type of object you are operating from const type to type.

Solution

Place the const keyword to the right of the member function declaration in both the class declaration and definition. Example 15-4 shows how to do this.

Example 15-4. Declaring a member function const

#include #include class RecordSet { public: bool getFieldVal(int i, std::string& s) const; // ... }; bool RecordSet::getFieldVal(int i, std::string& s) const { // In here, you can't modify any nonmutable data // members (see discussion) } void displayRecords(const RecordSet& rs) { // Here, you can only invoke const member functions // on rs }

 

Discussion

Adding a trailing const to a member declaration and its definition forces the compiler to look more carefully at what that member's body is doing to the object. const member functions are not allowed to invoke any nonconst operation on data members. If one does, compilation fails. For example, if, in RecordSet::getFieldVal, I updated a counter member, it wouldn't compile (assume that getFieldCount_ is a member variable of RecordSet):

bool RecordSet::getFieldVal(int i, std::string& s) const { ++getFieldCount_; // Error: const member function can't modify // a member variable // ... }

It can also help catch more subtle errors, similar to how const works in its variable-qualifier role (see Recipe 15.3). Consider this silly typo:

bool RecordSet::getFieldVal(int i, std::string& s) const { fieldArray_[i] = s; // Oops, I meant the other way around // ... }

Once again, the compiler will abort and give you an error because you are trying to change a member variable, and that's not allowed in const member functions. Well, with one exception.

In a RecordSet class, like the (bare-bones) one presented in Example 15-4, you would probably want member functions for moving forward and backward in the record set, assuming there is the notion of a "current" record. A simple way to do this is to keep an integer member variable that indicates the index of the current record; your member functions for moving the current record forward or backward increment or decrement this value:

void RecordSet::gotoNextRecord( ) const { if (curIndex_ >= 0 && curIndex_ < numRecords_-1) ++curIndex_; } void RecordSet::gotoPrevRecord( ) const { if (curIndex_ > 0) --curIndex_; }

Clearly this won't work if these member functions are const. Both update a data member. But without this behavior, consumers of the RecordSet class won't be able to scroll through a const RecordSet object. This is a reasonable exception to the const member function rules, so C++ has a mechanism to support it: the mutable keyword.

To allow curIndex_ to be updated by a const member function, declare it as mutable in the class declaration like this:

mutable int curIndex_;

This gives you a free pass to modify curIndex_ from wherever you like. This should be used judiciously, however, since it has the same effect as leaving your member function nonconst to begin with.

Using const as in Example 15-4 is an effective technique for guaranteeing that a member function does not change its object's state. In general, this is good practice because it communicates the behavior of the member function to users of the class, and because it keeps you honest by forcing the compiler to validate your assertion that a member function won't do something it shouldn't.

Категории