GotW #6a Solution: Const-Correctness, Part 1

const and mutable have been in C++ for many years. How well do you know what they mean today?

Problem

JG Question

1. What is a “shared variable”?

Guru Questions

2. What do const and mutable mean on shared variables?

3. How are const and mutable different in C++98 and C++11?

Solution

1. What is a “shared variable”?

A “shared variable” is one that could be accessed from more than one thread at the same time.

This concept is important in the C++ memory model. For example, the C++ memory model (the core of which is described in ISO C++ §1.10) prohibits the invention of a write to a “potentially shared memory location” that would not have been written to in a sequentially consistent execution of the program, and the C++ standard library refers to this section when it prohibits “modify[ing] objects accessible by [other] threads” through a const function, as we will see in #2.

2. What do const and mutable mean on shared variables?

Starting with C++11, const on a variable that is possibly shared means “read-only or as good as read-only” for the purposes of concurrency. Concurrent const operations on the same object are required to be safe without the calling code doing external synchronization.

If you are implementing a type, unless you know objects of the type can never be shared (which is generally impossible), this means that each of your const member functions must be either:

  • truly physically/bitwise const with respect to this object, meaning that they perform no writes to the object’s data; or else
  • internally synchronized so that if it does perform any actual writes to the object’s data, that data is correctly protected with a mutex or equivalent (or if appropriate are atomic<>) so that any possible concurrent const accesses by multiple callers can’t tell the difference.

Types that do not respect this cannot be used with the standard library, which requires that:

“… to prevent data races (1.10). … [a] C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.”—ISO C++ §17.6.5.9

Similarly, writing mutable on a member variable means what it has always meant: The variable is “writable but logically const.” Note what this implies:

  • The “logically const” part now means “can be used safely by multiple concurrent const operations.”
  • The “mutable” and “writable” part further means that some const operations may actually be writers of the shared variable, which means it’s inherently got to be correct to read and write concurrently, so it should be protected with a mutex or similar, or made atomic<>.

In general, remember:

Guideline: Remember the “M&M rule”: For a member variable, mutable and mutex (or atomic) go together.

This applies in both directions, to wit:

(1) For a member variable, mutable implies mutex (or equivalent): A mutable member variable is presumed to be a mutable shared variable and so must be synchronized internally—protected with a mutex, made atomic, or similar.

(2) For a member variable, mutex (or similar synchronization type) implies mutable: A member variable that is itself of a synchronization type, such as a mutex or a condition variable, naturally wants to be mutable, because you will want to use it in a non-const way (e.g., take a std::lock_guard<mutex>) inside concurrent const member functions.

We’ll see an example of (2) in Part 2, GotW #6b.

3. How are const and mutable different in C++98 and C++11?

First, let’s be clear: C++98 single-threaded code still works. C++11 has excellent C++98 compatibility, and even though the meaning of const has evolved, C++98 single-threaded code that uses the old “logically const” meaning of const is still valid.

With C++98, we taught a generation of C++ developers that “const means logically const, not physically/bitwise const.” That is, in C++98 we taught that const meant only that the observable state of the object (say, via its non-private member functions) should not change as far as the caller could tell, but its internal bits might change in order to update counters and instrumentation and other data not accessible via the type’s public or protected interface.

That definition is not sufficient for concurrency. With C++11 and onward, which now includes a concurrency memory model and thread safety specification for the standard library, this is now much simpler: const now really does mean “read-only, or safe to read concurrently”—either truly physically/bitwise const, or internally synchronized so that any actual writes are synchronized with any possible concurrent const accesses so the callers can’t tell the difference.

Although existing C++98-era types still work just fine in C++98-era single-threaded code for compatibility, those types and any new ones you write today should obey the new stricter requirement if they could be used on multiple threads. The good news is that most existing types already followed that rule, and code that relies on casting away const and/or using mutable data members in single-threaded code has already been generally questionable and relatively rare.

Summary

Don’t shoot yourself (or your fellow programmers) in the foot. Write const-correct code.

Using const consistently is simply necessary for correctly-synchronized code. That by itself is ample reason to be consistently const-correct, but there’s more: It lets you document interfaces and invariants far more effectively than any mere /* I promise not to change this */ comment can accomplish. It’s a powerful part of “design by contract.” It helps the compiler to stop you from accidentally writing bad code. It can even help the compiler generate tighter, faster, smaller code. That being the case, there’s no reason why you shouldn’t use it as much as possible, and every reason why you should.

Remember that the correct use of mutable is a key part of const-correctness. If your class contains a member that could change even for const objects and operations, make that member mutable and protect it with a mutex or make it atomic. That way, you will be able to write your class’ const member functions easily and correctly, and users of your class will be able to correctly create and use const and non-const objects of your class’ type.

It’s true that not all commercial libraries’ interfaces are const-correct. That isn’t an excuse for you to write const-incorrect code, though. It is, however, one of the few good excuses to write const_cast, plus a detailed comment nearby grumbling about the library vendor’s laziness and how you’re looking for a replacement product.

Acknowledgments

Thanks in particular to the following for their feedback to improve this article: mttpd, jlehrer, Chris Vine.

15 thoughts on “GotW #6a Solution: Const-Correctness, Part 1

  1. Just a small fix:

    s/”const now really does “read-only, or safe to read concurrently”—either truly physically/bitwise const, or internally synchronized so that any actual writes a synchronized with any possible concurrent non-const accesses so the callers can’t tell the difference”/”const now really does *MEAN* “read-only, or safe to read concurrently”—either truly physically/bitwise const, or internally synchronized so that any actual writes *ARE* synchronized with any possible concurrent non-const accesses so the callers can’t tell the difference”
    // emphasis only to be helpful, no shouting intended ;-)

    Incidentally, any comments on constexpr-correctness (or is that reserved for the next part? :])?

    // BTW, there seems to be a few copies of this post.

  2. Regarding the shared variable question–the following note from the standard (17.6.4.10/[res.on.objects) may also be of help in underscoring the importance of this concepts:
    “Modifying an object of a standard library type that is shared between threads risks undefined behavior unless objects of that type are explicitly specified as being sharable without data races or the user supplies a locking mechanism.”

    // Personally, I also like to think in terms of the shared-vs.-private distinction in the sense its used in OpenMP, but perhaps this is somewhat too API-specific for this question.

  3. Incidentally, my second comment is a nice example of Muphry’s law (spelling intentional) in action ;-)
    // this CONCEPT; IT’S used; …good grief :D

    @Herb: any chance for an edit-own-comments feature? :-)

  4. Suppose you have a container like std::map and you want to populate it once at startup and then have multiple threads do lookups later.

    This is easy enough to accomplish with the new definition of const. Just create the map, add stuff to it, start your threads, and then expose only a const reference to it to the threads. Since the threads only use const methods, the lookups are guaranteed to be thread safe without requiring the callers to use mutexes or any other external synchronization. One could imagine an implementation of std::map possibly moving nodes around the tree during lookup as an optimization. With const, this behavior would either be prohibitied or would have to be internally synchronized.

    But what if you want the threads to be able to modify the widgets? Assuming the widgets modify operations themselves are properly synchronized in some other way, this is perfectly safe as the internal operations of the std::map (or any other key/value mapping data structure) depends only on the keys, not the values.

    Unfortunately the const version of find() returns a const_iterator which only provides access to a const version of the widget. The only way around this problem is to either do a const_cast on the const widget& or do some kind of indirection by storing Widget* in the container or some other iterator/index into another storage container for the widgets.

    It seems like const is a little too strong here, in that the constness of the container also “infects” the elements of the container. Sometimes this is what you want, sometimes not. I think in C++98 it made a little more sense but now that const has also gained “thread safe” semantics this weaker form of const may be desirable.

    Was this usage pattern ever considered for std::map/unordered_map and other containers? One could even imagine doing the same with a vector, where you wanted to allocate a bunch of object contigously in memory beforehand and then read and modify them later without being able to insert or remove anything from the vector. I don’t think any sane implementation of vector::operator[]/vector::at() would do anything more than a bounds check and array reference but it still would be technically not standards compliant to read from a std::vector without either using a mutex or a const method.

    I’m thinking the best solution for this problem for now would be to wrap the container in another class which calls the const methods for lookup and const_cast’s the values to non-const before returning them. Any thoughts?

  5. “Starting with C++11, const on a variable that is possibly shared means read-only, safe to read concurrently without external synchronization.”

    This is only true if no threads have access to the shared object in a non-const context. If a non-const shared object is passed to a library function by reference to const, without synchronization that function’s read on the object is not safe if another thread might modify it.

    What I think this should say is that “const means read-only, and safe to read concurrently without external synchronization if no other thread has write access to the shared object concerned in a non-const context”.

  6. @Chris: Thanks. I figured that was implicit, but it can’t hurt to be explicit. I’ve rewritten the first part of #2 to make this clearer (I hope).

  7. “Concurrent const operations on the same object are required to be safe without the calling code doing external synchronization.”

    This is the sentence that was missing from the original you don’t know const and mutable video/discussion.
    Everything makes sense now and this is a great feature of const.

  8. If const is not bitwise const anymore why not give it some new meaning, e.g. “CONSistenT” in terms of safe to be called from multiple threads.
    Then our compiler will check for us whether we mixiung up thread safe and not thread safe methods.

    Below is an example of using Lock class (based on pthread mutex), class SafeData which uses lock for implement thread safe access to some data and class UseSafeData which shows possible usage of SafeData.

    At the first glance making all methods const seems ridiculous, but the benefit is that compiler will disallow calls of unsafe methods from safe methods.
    The guidelines here are slightly different from above:

    * all thread safe methods and members are defined using “const”
    * define members which are not thread safe and must be synchronized as “mutable”

    This implies that synchronization primitives (locks, critical sections, …) are themselves thread safe and should provide “const” interface. For Lock class below methods lock() and unlock() are const. They also should be declared as const in other classes to restrict the usage to thread safe methods.

    The class SafeData provides two methods for data manipulation, one is thread safe and one is not: setDataSafe() and setData().

    The class UseSafeData has two instances of SafeData and again two methods useSafe() and useUnsafe() (first is meant to be thread safe, second not).
    Now the compiler will check for incorrect using of SafeData’s unsafe methods inside useSafe() method thus preventing us from accidently calling not thread safe method from a thread safe one. (See content of methods useSafe() and useUnsafe() ).

    What do You think about this approach? Are there any pitfalls with new C++11?

    #include <pthread.h>
    
    /// Basic locking class
    /// const=thread safe
    class Lock
    {
    protected:	
    	mutable pthread_mutex_t mutex;	///< pthread mutex must be mutable
    public :
    	
    	Lock()
    	{
    		 pthread_mutex_init(&mutex, NULL);
    	}
    	virtual ~Lock()
    	{
    		pthread_mutex_destroy(&mutex);
    	}
    	
    	/// this method is thread safe and thus const
    	void lock() const
    	{
    		 pthread_mutex_lock(&mutex);
    	}
    	
    	/// this method is thread safe and thus const
    	void unlock() const
    	{
    		 pthread_mutex_unlock(&mutex);
    	}
    };
    
    /// Example of class which is used in multi-threading context and has some non atomic data which requires locking.
    /// const = thread safe
    class  SafeData
    {
    	const Lock lock; ///< const will restrict interface of Lock to const methods 
    	
    	mutable int dataA; ///< members which want to be protected by locks. Must be mutable.
    	mutable int dataB; 
    public:
    
    	/// this method is thread safe (is const)
    	void setDataSafe(int d) const
    	{
    		lock.lock();
    		printf("setDataSafe(%d) const\n",d);
    		dataA = d;
    		dataB = d*d;
    		lock.unlock();
    	}
    	
    	/// this method is not thread safe 
    	void setData(int d)
    	{
    		printf("setData(%d)\n",d);
    		dataA = d;
    		dataB = d*d;
    	}
    };
    
    
    /// This class is also used by multiple threads and is using other thread-safe classes
    /// const = thread safe
    class UseSafeData
    {
    	
    	/// Const will restrict interface to safe methods
    	const SafeData dataConst;
    	
    	/// Non-const will allow unsafe methods to be called from unsafe methods only
    	SafeData data;
    	
    public:
    
    	/// thread safe method (has const)
    	void useSafe() const
    	{
    		// following two lines are OK: calling safe methods from safe methods.
    		dataConst.setDataSafe(1);
    		data.setDataSafe(2);
    		
    		// following will be prevented by compiler
    		// dataConst.setData(3);
    		
    		// following will be prevented by compiler because we are trying to call unsafe method from safe method.
    		// data.setData(4);
    	}
    	
    	/// not thread safe method (has no const)
    	void useUnsafe()
    	{
    		
    		dataConst.setDataSafe(5);
    		data.setDataSafe(6);
    		
    		// following will be prevented by compiler because we declared dataConst as const thus restricting usage to safe methods
    		// dataConst.setData(7);
    
    		// this is allowed because we are not in safe method and data's interface allows unsafe calles
    		data.setData(8);		
    
    	}
    
    };
    
    
    
  9. gcc 4.8 -std=gnu++11

    http://en.cppreference.com/w/cpp/locale/locale
    static const locale & classic();

    std::isdigit( jakis_znak, std::locale::classic() );

    valgrind:
    Possible data race during write of size 8 at 0xB9B9E20 by thread #5
    ==62666== Locks held: none
    ==62666== at 0xB921980: std::locale::locale(std::locale::_Impl*) (in
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
    ==62666== by 0xB923E4E: std::locale::classic() (in
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)

    ==62666== This conflicts with a previous write of size 8 by thread #3
    ==62666== Locks held: none
    ==62666== at 0xB921980: std::locale::locale(std::locale::_Impl*) (in
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)
    ==62666== by 0xB923E4E: std::locale::classic() (in
    /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.18)

Comments are closed.