
On Jul 22, 2004, at 7:54 AM, Peter Dimov wrote:
Howard Hinnant wrote:
I don't dislike these helper functions. But I do dislike the idea that they should be the only way to construct-and-try or construct-with-time. The problem is that we have 3 different types of locks, and I want to be able to construct-and-try in a generic-lock function:
template <class Lock> void foo() { Lock lk(m, try_lock); ... }
The above code should work for scoped_lock, sharable_lock and upgradable_lock.
template<class Lock> void foo() { bool const deferred = false;
Lock lock( m, deferred );
if( lock.try_lock() ) { // locked } }
as usual. Clean separation between lock type (scoped/exclusive, shareable, upgradable) and lock operations (unconditional blocking lock, try lock, timed lock, unlock).
Ok. By that reasoning the try_lock(Mutex&) helper function is also unnecessary. The ability to construct an unlocked lock is fundamental, and everything else can be built in terms of that, including locked and timed-lock constructors/helpers. But the other constructors / helpers are convenient. Let's review: // no try-ctor, no try-helper func template <class Lock> void foo1() { Lock lk(m, false); if (lk.try_lock()) ... } // use a try_lock helper func template <class Lock> void foo2() { if (Lock lk = try_lock(m)) ... } Notation is a little more compact, but it only works for whatever type Lock that try_lock() returns (presumably scoped_lock). // use a generic try_lock helper func template <class Lock> void foo3() { if (Lock lk = try_lock<Lock>(m)) ... } This is now generalized to work for all Lock types. // use a try-lock ctor template <class Lock> void foo4() { if (Lock lk = Lock(m, try_lock)) ... } This also works for all Lock types. Solution 1 is slightly less efficient than solutions 3 and 4 (solution 2 isn't really a solution). The reason is that foo1 expands to: m_ = m; locked_ = lock_it; if (locked_) m_.lock(); locked_ = m_.try_lock(); if (locked_) ... I.e. the Lock(m, bool) constructor checks the value of the bool to decide whether to lock or not. In contrast, solutions 3 and 4 look more like: m_ = m; locked_ = m_.try_lock(); if (locked_) ... I.e. no attempt is made to call m_.lock(). Actually solution 3 will only be this efficient if the try_lock helper function doesn't construct its Lock via the code shown in foo1, but instead uses something equivalent to the Lock(m, try_lock) constructor. So 3 and 4 look the best to me, both in terms of readability, and in terms of more optimized code (size and speed). And it seems like the best way to efficiently implement 3 is by using the same constructor that 4 uses. If you follow up to this point, then given 4 exists anyway, is 3 worth the trouble? -Howard