
Phil Endecott schrieb:
Hi Klaus,
Hi Phil, sorry for being quiet for some time, I was on holidays...
I finally got around to looking at this. Yes, it does what I expect it to.
One detail I noticed was that you make the mutex mutable, so that you can lock a const object. This is something that I have worried about, because I fear that there are "gotchas". Do any experts have opinions about this?
Hmm, what "gotchas" could you think of? The idea is that e.g. one thread shares a lockable and is allowed to write to it and other threads share a lockable but only get read access - this is expressed with the const-qualifier on the lockable. I would think of different ways, e.g. to refine const/non-const with derivation: <code> template<typename T, typename T_mutex> struct lockable: boost::mpl::eval_if< std::tr1::is_const<T>, lockable_base<T_mutex>, lockable<const T>
::type { // ... }; </code>
I said before that I found my own locking pointer (or locking reference) was more verbose than explicitly locking the mutex and accessing the data, and so have not used it much. I've now remembered
Do you think that using the lock_acquirer is too verbose, too? I admit that it is verbose... The intent is to force a named lvalue lock_acquirer object. I like the idea of Frank's suggestion for an operator *().
another practical problem with it. Here's the code without Lockable<>:
void start_updater(mutex& m, int& val) { // spawn a thread that periodically locks m and changes val }
mutex m; array<int,100> values;
for (int i=0; i<100; ++i) { start_updater(m,values[i]); }
The point is that all of the values share a mutex. Our Lockable<> classes could be used here if you wanted one mutex per value, but that could be wasteful and perhaps wrong. How can we adapt Lockable<> to work in this sort of situation? I'm wondering about something like this:
Lockable< array<int,100 > values;
for (int i=0; i<100; ++i) { start_updater(LockableRef<int>(values,values[i]); }
i.e. LockableRef contains a reference to the mutex and one element of its "parent" Lockable<>, and presents the same interface as a data-full Lockable<>.
But because Lockable and LockableRef aren't actually the same type, I can't write
void start_updater(Lockable<int> i);
and use it with both.
Is this something that you have thought about?
Well, I didn't think about this one. But what do you think of the following? <code> template<typename T_type, typename T_mutex> struct lockable //: ... { // ... // return a lockable_ref, see below lockable_ref<std::tr1::remove_extent<T_type>, T_mutex> operator [](std::size_t idx) { return lockable_ref<std::tr1::remove_extent<T_type>, T_mutex>(access_volatile()[idx], mutex()); } }; // a lockable_ref like I imagine it template<typename T_type, typename T_mutex> struct lockable_ref: lockable_tag { // copy construct from rvalue and lvalue ref lockable_ref(lockable_ref&& rvalue): //parent_type(rvalue.mutex()), m_mutex_ref(rvalue.mutex()), m_obj_ref(rvalue.access_volatile()) {} // implicitly construct from lockable typedef lockable<T_type, T_mutex> lockable_type; lockable_ref(lockable_type& rvalue): //parent_type(rvalue.mutex()), m_mutex_ref(rvalue.mutex()), m_obj_ref(rvalue.access_volatile()) {} // construct from a variable and a mutex lockable_ref(T_type& rvalue, T_mutex& rmutex): //parent_type(rvalue.mutex()), m_mutex_ref(rmutex), m_obj_ref(rvalue) {} T_mutex& m_mutex_ref; T_type& m_obj_ref; }; </code> now you can write start_updater like this: <code> void start_updater(lockable_ref<int, mutex> i) { } lockable<array<int, 100>, mutex> values; lockable<int, mutex> one_value; for (int i=0; i<100; ++i) { start_updater(values[i]); } start_updater(one_value); </code> Klaus