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.

Leave a Reply

Your email address will not be published. Required fields are marked *