Should mutexes be volatile?

Not sure if this is a style issue or something that has a strict rule ...

If I want the public method interface to be as const as possible, but to make object streams safe, should mutable mutexes be used? In general, is this a good style, or should a non-const interface be preferred? Please confirm your opinion.

+44
c ++ mutex mutable
Nov 08 '10 at 19:36
source share
2 answers

[edited answer]

In principle, using const methods with mutable mutexes is a good idea (do not return links, return by value), at least to indicate that they do not modify the object. Mutexes should not be const, it would be a shameless lie to define lock / unlock methods as const ...

Actually this (and memoization) is the only fair use that I see in the mutable key.

You can also use a mutex that is external to your object: arrange all your methods as reentrant and manage the lock itself: { lock locker(the_mutex); obj.foo(); } { lock locker(the_mutex); obj.foo(); } { lock locker(the_mutex); obj.foo(); } not that hard to type, and

 { lock locker(the_mutex); obj.foo(); obj.bar(42); ... } 

It has the advantage of not requiring two mutex locks (and you are guaranteed that the state of the object has not changed).

+25
Nov 08 '10 at 19:38
source share

Hidden question: where do you put the mutex protecting your class?

As a result, let's say you want to read the contents of an object that is protected by a mutex.

The read method must be semantically const, because it does not change the object itself. But in order to read the value, you need to lock the mutex, extract the value, and then unlock the mutex, that is, the mutex itself must be changed, which means that the mutex itself cannot be "const".

If the mutex is external

Then everything is fine. An object can be "const", and a mutex should not be:

 Mutex mutex ; int foo(const Object & object) { Lock<Mutex> lock(mutex) ; return object.read() ; } 

IMHO, this is a bad decision, because anyone can reuse a mutex to protect something else. Including you. In fact, you will betray yourself, because if your code is quite complicated, you will simply be confused by the fact that this or that mutex accurately protects.

I know: I was a victim of this problem.

If the mutex is internal

For encapsulation purposes, you must place the mutex as close as possible to the protected object.

Usually you will write a class with a mutex inside. But sooner or later you will need to protect the complex STL structure or something else written by others without a mutex inside (which is good).

A good way to do this is to get the source object with the inheriting template by adding a mutex function:

 template <typename T> class Mutexed : public T { public : Mutexed() : T() {} // etc. void lock() { this->m_mutex.lock() ; } void unlock() { this->m_mutex.unlock() ; } ; private : Mutex m_mutex ; } 

So you can write:

 int foo(const Mutexed<Object> & object) { Lock<Mutexed<Object> > lock(object) ; return object.read() ; } 

The problem is that it will not work, because object is a constant, and the lock object calls the non-const lock and unlock methods.

Dilemma

If you think that const limited to const bitwise objects, then you are screwed up and should return to the “external mutex solution”.

The solution is to recognize that const is more of a semantic classifier (like volatile when using class methods as a classifier). You are hiding the fact that the class is not completely const , but still be sure to provide an implementation that keeps the promise that the significant parts of the class will not be changed when the const method is called.

Then you should declare mutex mutable and const lock / unlock methods:

 template <typename T> class Mutexed : public T { public : Mutexed() : T() {} // etc. void lock() const { this->m_mutex.lock() ; } void unlock() const { this->m_mutex.unlock() ; } ; private : mutable Mutex m_mutex ; } 

The internal solution for the mutex is a good IMHO: the presence of objects declared one next to the other in one hand, and with their combination in a wrapper in the other hand, is the same at the end.

But aggregation has the following advantages:

  • This is more natural (you lock the object before accessing it)
  • One object, one mutex. Since the code style forces you to follow this pattern, it reduces the risk of deadlocks, since one mutex will protect only one object (and not several objects that you really don’t remember), and one object will be protected only by one mutex (and not several mutexes that should be blocked in the correct order)
  • The mutex class above can be used for any class.

So, keep the mutex as close as possible to the mutexed object (for example, using the Mutexed construct above) and go for the mutable qualifier for the mutex.

Edit 2013-01-04

Herb Sutter seems to have the same point of view: his idea of ​​the “new” const and mutable values ​​in C ++ 11 is very useful:

http://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/

+39
Nov 08 '10 at
source share



All Articles