views:

193

answers:

5

I want to execute a read-only method on an object marked as const, but in order to do this thread-safely, I need to lock a readers-writer mutex:

const Value Object::list() const {
  ScopedRead lock(children_);
  ...
}

But this breaks because the compiler complains about "children_" being const and such. I went up to the ScopedRead class and up to the RWMutex class (which children_ is a sub-class) to allow read_lock on a const object, but I have to write this:

inline void read_lock() const {
  pthread_rwlock_rdlock(const_cast<pthread_rwlock_t*>(&rwlock_));
}

I have always learned that const_cast is a code smell. Any way to avoid this ?

+6  A: 

Make the lock mutable.

James McNellis
+13  A: 

Make the lock mutable

mutable pthread_rwlock_t rwlock;

This is a common scenario in which mutable is used. A read-only query of an object is (as the name implies) an operation that should not require non-const access. Mutable is considered good practice when you want to be able to modify parts of an object that aren't visible or have observable side-effects to the object. Your lock is used to ensure sequential access to the object's data, and changing it doesn't effect the data contained within the object nor have observable side-effects to later calls so it is still honoring the const-ness of the object.

RC
Another way to explain it: C++ method const is logical constness, not physical constness. It's okay to change the physical state (take a lock, modify a cache) as long as logically, the object state is unchanged.
R Samuel Klatchko
By logically, we mean 'from an observer outside' >> ie if there is no effect on the result of the public / protected methods of the object (Yes, protected too, since a Derived class should not know about the implementation of its Base).
Matthieu M.
Thanks for all the detailed answers and comments. I've learned more on C++ in a few weeks on stackoverflow then reading tons of C++ gotchas and other books in months.
Gaspard Bucher
A: 

To solve the actual problem, declare the lock as mutable.

The following is now my professional opinion:

The compiler is right to complain, and you are right to find this mildly offensive. If performing a read-only operation requires a lock, and locks must be writeable to lock, then you should probably make the read-only query require non-const access.

EDIT: Alright, I'll bite. I've seen this kind of pattern cause major perf hits in places you would not expect. Does anyone here know how tolower or toupper can become a major bottleneck if called frequently enough, even with the default ASCII locale? In one particular implementation of the C runtime library built for multithreading, there was a lock taken to query the current locale for that thread. Calling tolower on the order of 10000 times or more resulted in more of a perf hit than reading a file from disk.

Just because you want read-only access doesn't mean that you should hide the fact that you need to lock to get it.

MSN
A `const` function should not change any observable behavior of a class, but it is often reasonable to change member variables. In this case, it appears that you're modifying something to lock out other threads during a read operation. The read operation is `const`, I assume, and the changed variable is only to sequence access, so it probably should be `mutable`.
David Thornley
That's true, but I argue that invoking a lock for read-only access is something that you want to be observable.
MSN
I was trying to alter the lock so that read-only access works on const locks.
Gaspard Bucher
I understand that. What I'm saying is that needing to lock to provide read-only access is also a code smell. At least to me. Granted in the context of most stackoverflow questions that doesn't address your question, but I felt it bears mentioning.
MSN
@MSN: No, using an exclusive access lock does not change the observable state of the object. Any operation performed on the object before and after the const operation takes place will yield exactly the same result. The fact that it affects how fast you perceive the state and the state you perceive are completely unrelated. Then, having to lock in a read operation is not a code smell, but rather a necessity if the read operation is not atomic. Else a writing thread could change the object in between and you could end up reading invalid values.
David Rodríguez - dribeas
I understand that it isn't part of the observable state of the object.That isn't my point. In fact, my point has nothing to do with this question apparently.I understand why you would want to do this. Based on my experience, it's the wrong thing to do. Having a single class be both responsible for a valid read-only view as well as synchronizing access to its internals is a code smell **to me**. I even stated why this matters even in the most trivial of cases.In other words, having an object be in charge of its own thread safety probably means you are doing too much in that object.
MSN
A: 

Yes, use mutable. It's designed for this very purpose: Where the entire context of the function is const (i.e. an accessor or some other logically read-only action.) but where some element of writable access is needed for a mutex or reference counter etc.

The function should be const, even if it does lock a mutex internally. Doing so makes the code thread-neutral without having to expose the details, which I presume is what you're trying to do.

There are very few places where const_cast<> needs to be legitimately used and this isn't one of them. Using const cast on on an object, especially in a const function is a code maintenance nightmare. Consider:

token = strtok_r( const_cast<char*>( ref_.c_str() ), ":", &saveptr );

In fact, I'd argue that when you see const_cast in a const function, you should start by making the function non-const (very soon after you should get rid of the const_cast and make the function const again though)

Robin Welch
A: 

Well, if we are not allowed to modify the declaration of the variable, then const_cast comes to the rescue. If not, making it mutable is the solution.

Jagannath