In a previous post about locks in C++11 I have shown a dummy implementation of a container class that looked like this (simplified):
template <typename T> class container { std::recursive_mutex _lock; std::vector<T> _elements; public: void dump() { std::lock_guard<std::recursive_mutex> locker(_lock); for(auto e : _elements) std::cout << e << std::endl; } };
One can argue that the dump() method does not alter the state of the container and should be (logically) const. However, as soon as you make it const you get the following error:
‘std::lock_guard<_Mutex>::lock_guard(_Mutex &)’ : cannot convert parameter 1 from ‘const std::recursive_mutex’ to ‘std::recursive_mutex &’
The mutex (regardless which of the four flavors available in C++11) must be acquired and released and the lock() and unlock() operations are not constant. So the argument the lock_guard takes cannot be logically const, as it would be if the method was const.
The solution to this problem is to make the mutex mutable. Mutable allows changing state from const functions. It should however be used only for hidden or “meta” state (imagine caching computed or looked-up data so a next call can complete immediately, or altering bits like a mutex that only complement the actual state of an object).
template <typename T> class container { mutable std::recursive_mutex _lock; std::vector<T> _elements; public: void dump() const { std::lock_guard<std::recursive_mutex> locker(_lock); for(auto e : _elements) std::cout << e << std::endl; } };
An important thing to note is that in C++11 both const and mutable imply thread-safety. I recommend this C++ and Beyond talk by Herb Sutter called You don’t know [blank] and [blank].