Boost logo

Boost :

From: Howard Hinnant (hinnant_at_[hidden])
Date: 2004-07-22 09:22:35


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


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk