Boost logo

Boost :

From: Bronek Kozicki (brok_at_[hidden])
Date: 2004-07-23 14:53:09


Howard Hinnant wrote:

> If we introduce "try_move" it could be a similarly simple helper which
> just returns an auto_ptr-ref-like object tagged with the "try"
> information. Not that I'm 100% dead set on try_move, I'm not.

the point is that "move" funcion in some situations (construction of
temporary object) is not necessary. And when it's necessary
(construction from lvalue, taking over it's property) it has well known
meaning. Proposed function "try_move" does not have such well-defined
meaning, and there is no way you could avoid it (just like we can avoid
"move" when creating object from temporary) if we keep this constructor:

scoped_lock& operator=(try_rvalue_ref<upgradable_lock<mutex_type> > r);

> Yes, but my concern is what does the syntax look like when s4 already
> exists and you try to upgrade to it by assigning u4 to it:
>
> s4 = u4; // ??? Try to upgrade (non-blocking) u4 to s4
>
> This isn't a hypothetical scenario, I have a use case for it. I've
> suggested
>
> s4 = try_move(u4);
>
> and am open to other syntax suggestions.

Good point. Maybe upgradable_lock should have member function
try_upgrade(), that would try to give away ownership of mutex object to
temporary scoped_lock? Then you would have:

   s4 = u4.try_upgrade(); // non-blocking upgrade

You could have also:

   s4 = u4.upgrade(); // blocking upgrade

which would have result identical to:

   s4 = move(u4); // blocking upgrade

Moreover, we could also have:

   // blocking upgrade with timeout
   s4 = u4.try_upgrade(some_timeout_value);

which is (I think) something new and quite useful?

> I believe this still requires Lock to be explicitly supplied in each
> case. That's doable. But is it sufficiently desirable?

I think so. You need to supply type of constructed lock, but you do not
need to know (or supply) type of Mutex class; it's deduced from function
parameter. When writing code like:

   const lock_base& l = lock<scoped_lock>(m);

you do not need to know type of "m". You just need to know what template
class should be used to construct lock, without taking care of template
parameter.

Moreover, with some simple SFINAE trickery you could even use above code
when "m" is not mutax at all, but upgradable_lock instead (of course
this would compile different overload of "lock"). As result, we would
have familty of functions that could be used to create lock from mutex
and upgrade upgradable_lock. In last case template template parameter
<scoped_lock> is somehow redudand, thus it's not really great benefit.
On the other hand I think that genericity of such functions is appealing.

> Could you flesh this out with a little more complete code. Sorry, I'm
> just not following. For instance the first line appears to bind an
> rvalue to a non-const reference. Are we just missing a const on the
> lock_base& or is there something else going on?

lack of "const" is just my mistake :> See attached file for more
complete example.

> I think this will have an uphill battle. This code:
>
> {
> exclusive_lock some_lock(some_mutex);
> // assume some_mutex is locked here
> }
>
> is just too easy to write (and be wrong).

You are definitely right. What about supplying just two constructors:

explicit scoped_lock(Mutex&);
explicit scoped_lock(Mutex&, deferred_enum);

Best regards

B.


#include <cassert>

class lock_base
{
        bool* locked_;
        void* mutex_;

        protected:
        lock_base(bool * locked, void* mutex) : locked_(locked), mutex_(mutex) {}
        lock_base(const lock_base& src) : locked_(src.locked_), mutex_(src.mutex_) {}
        ~lock_base() {}

public:
        bool locked() const {return *locked_;}

        // bool for brevity; should be safe_bool instead
        operator bool() const {return locked();}

        bool same(const lock_base& oth) const
        {
                return oth.mutex_ == mutex_;
        }
};

bool operator== (const lock_base& lh, const lock_base& rh)
{
        return lh.same(rh);
}

bool operator!= (const lock_base& lh, const lock_base& rh)
{
        return !(lh == rh);
}

template <typename Mutex>
class scoped_lock : public lock_base
{
        bool locked_;
        Mutex* mutex_;

public:
        scoped_lock(Mutex& m) : lock_base(&locked_, mutex_), locked_(false), mutex_(&m)
        {
                lock();
        }

        // "bool deferred" for simplicity
        scoped_lock(Mutex& m, bool deferred) : lock_base(&locked_, mutex_), locked_(false), mutex_(&m)
        {
                if (!deferred)
                        lock();
        }

        // copy constructor and assignment operator skipped for brevity

        ~scoped_lock()
        {
                if (locked_)
                        unlock();
        }

        void lock()
        {
                mutex_->lock();
                locked_ = true;
        }

        void try_lock()
        {
                locked_ = mutex_->try_lock();
        }

        void try_lock(unsigned long timeout_ms)
        {
                locked_ = mutex_->try_lock(timeout_ms);
        }

        void unlock()
        {
                mutex_->unlock();
                locked_ = false;
        }
};

class fast_mutex
{
        void lock() {}
        bool try_lock() {return true;}
        void unlock() {}

        friend class scoped_lock<fast_mutex>;
};

class waitable_mutex
{
        void lock() {}
        bool try_lock() {return true;}

        // "unsigned long timeout_ms" for brevity
        bool try_lock(unsigned long timeout_ms) {return false;}
        void unlock() {}

        friend class scoped_lock<waitable_mutex>;
};

template <template <typename _Mutex> class Lock, typename Mutex>
Lock<Mutex> lock(Mutex& mutex)
{
        Lock<Mutex> tmp(mutex);
        return tmp;
}

template <template <typename _Mutex> class Lock, typename Mutex>
Lock<Mutex> try_lock(Mutex& mutex)
{
        Lock<Mutex> tmp(mutex, true);
        tmp.try_lock();
        return tmp;
}

template <template <typename _Mutex> class Lock, typename Mutex>
Lock<Mutex> try_lock(Mutex& mutex, unsigned long timeout_ms)
{
        Lock<Mutex> tmp(mutex, true);
        tmp.try_lock(timeout_ms);
        return tmp;
}

fast_mutex m1;
waitable_mutex m2;

int main()
{
        scoped_lock<fast_mutex> l1(m1);

// I do not need to know type of "m1" here in order to create lock for it
        const lock_base& l2 = lock<scoped_lock>(m1);

        {
// I do not need to know type of "m1" and even if it supports "try_lock"
                const lock_base& l3 = try_lock<scoped_lock>(m1);
                if (l3)
                {
                        ; // got m1
                }
        }

        {
// I do not need to know if m2 is waitable; if it isn't
// there would be just compilation error
                const lock_base& l4 = try_lock<scoped_lock>(m2, 200);
                if (l4)
                {
                        ; // got m2
                }

                if (l4 == l1)
                        ; // well, this time mutexes are not equal
        }

}


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