Shared memory problems with const

The interpretation of const in C++ is a matter of confusion. The language defines const to mean that the representation of an object does not change. Many people argue that this violates the data abstraction principle that is so important to object-oriented programming--clients shouldn't care if the representation changes, only if the semantic state of the object changes. This is an important point because, for example, a class can have an internal cache that changes on a call to a member function, but doesn't change the semantic state. Should that member function be const or not? Should it change when the implementation changes?

A C++ compiler sometimes needs to know this information. Also, it's important that you know this when placing objects in read-only memory (such as a shared area that only has read access), or when worrying about concurrency (acquiring a shared rather than exclusive lock). It's not reassuring to know that the semantic state is unchanged if you get a bus error, or worse, a subtle race condition.

You can solve the concurrency issue by protecting the object internally with a semaphore, but you really cannot solve the read-only memory problem. However, there are some workarounds you can use:

You can have both const and non-const versions of the same function. Because the C++ function overloading mechanism is sensitive to the const nature of member functions, the compiler uses one for a const object, and the other for a non-const object. This allows the non-const version to change the cache, while the const version cannot. The only problems are the usual ones for overloading, such as the way it interacts with overriding (see "Issues in overloading and overriding classes" on page 136). Also, there is less benefit for things like caching when you use const. This overloading applies to function arguments as well.

Use an accessor object (a type of surrogate, see page 125) to get the performance benefits of a cache and not change the object. Any modifiable state (like a cache) goes in the surrogate object--which might need to be a friend of the object to which it refers. The collection TIterators are an example of accessor objects--they are modified as you iterate, but the underlying collection is unchanged.

NOTE This technique is also useful for general concurrency issues, as it sometimes removes the need for synchronization. For example, iterators from several different threads can freely access a collection that does not change.

If you must modify an object inside a const member function, cast the this pointer to a non-const pointer or declare the member in question as mutable (a new ANSI feature not yet added to many C++ compilers). If you do this, however, you must document that the function may not be called for an object in read-only memory, and you must either protect the object's state with an internal semaphore, or document that the function modifies the object internally (for example, it's not multithread safe). Taligent engineers must check with an architect first.

[Contents] [Previous] [Next]
Click the icon to mail questions or corrections about this material to Taligent personnel.
Copyright©1995 Taligent,Inc. All rights reserved.

Generated with WebMaker