const
-antly Changing
In addition to the virtual
and inline
method modifiers, C++ also allows the addition of the const
modifier on class methods. The const
modifier indicates the method does not alter the state of class member variables.
class MyNumber { public: int getValue() const { return mValue; } void setValue(int inValue) { mValue = inValue; } private: int mValue; };
The implementation of getValue()
, as long as it is declared const
, cannot alter the value of mValue; the method simply has read-only access to all member variables. Declaring a method as const
is helpful because it allows us to handle read-only instances of the MyNumber
class:
void printMyNumber(const MyNumber& inNumber) { cout << inNumber.getValue(); // We can't call inNumber.setValue() because our reference // is to a const MyNumber object and setValue() is not a // const method. } ... MyNumber x; x.setValue(14); // We pass in a reference to a constant object, x. printMyNumber(x); // We know the value of x is still 14.
But what if we want to use a locking mechanism to make our class thread-safe?
class MyLock { public: void lock(); void unlock(); }; class MyNumber { public: int getValue() const { mLock.lock(); const int val = mValue; mLock.unlock(); return val; } void setValue(int inValue) { mLock.lock(); mValue = inValue; mLock.unlock(); } private: MyLock mLock; int mValue; };
Can we do this? Notice the lock()
method on MyLock
isn't const
, presumably because the internal state of the MyLock
object isn't preserved during its execution (obviously the state of the lock changes). The compiler will report an error when it encounters mLock.lock()
within a const
method. We could remove the const
modifier from getValue()
, but then we can no longer pass around const
references or pointers to MyNumber
when we need to retrieve its value.
We now have a question to consider: What do we mean when we say a const
method does not alter the internal state of an object? From within MyNumber
, we can say getValue()
no longer preserves the state of MyNumber
because the bits inside mLock
are changing. But from another perspective (outside of MyNumber
), does anyone really care about the changing state of mLock
? It's an internal object that is invisible as far as any code using MyNumber
is concerned. It's an implementation detail that remains hidden from the outside world. So how can we enjoy the benefits of keeping getValue()
as a const
-modified method.
Fortunately, C++ provides a solution to this problem. One possibility would be recasting the this
pointer to effectively ignore the const
modifier:
int MyNumber::getValue() const { const_cast<MyNumber*>(this)->mLock.lock(); int val = mValue; const_cast<MyNumber*>(this)->mLock.unlock(); return val; }
However, this is pretty ugly. A better solution is to simply declare the mLock
member variable as mutable
:
class MyNumber { ... private: mutable MyLock mLock; int mValue; };
The mutable
keyword lets the compiler know the state of the member variable can be altered even from within a const
method. Obviously, the mutable
modifier could be misused - why not declare all methods as const
and all member variables as mutable
? The intent is to use mutable only when changes to the internal state of an object cannot be observed from outside the object. Thread-safe locks could be one example of such a situation. The mutable
keyword might also be used to update internal statistics for the class - so you could keep track of how many times the getValue()
method had been called. You might also use the mutable
keyword to cache the result of a computationally expensive operation:
class MyNumber { public: double getSquareRootValue() const { // Track some statistics. mSqrtCount++; // Cache the square root if we don't already have it. if (mSqrtValue < 0) { mSqrtValue = sqrt(mValue); } return mSqrtValue; } void setValue(double inValue) { mValue = inValue; mSqrtValue = -1; } private: double mValue; mutable double mSqrtValue; mutable int mSqrtCount; };
The use of mutable
allows us to maintain what can be referred to as "conceptual const-ness" (or "logical const-ness") instead of "bitwise const-ness". That is to say, the bits within an object may change, but the conceptual (or observable) state of the object remains constant.