Boost logo

Boost :

From: William E. Kempf (williamkempf_at_[hidden])
Date: 2002-05-17 15:03:08


----- Original Message -----
From: "Peter Dimov" <pdimov_at_[hidden]>

> From: "William E. Kempf" <williamkempf_at_[hidden]>
> >
> > This doesn't cause you to not know the exact lifetime of the lock... it
> just
> > means the programmer had better understand the rules of move semantics.
> In
> > other words, as long as you pay attention to how move semantics work
this
> is
> > a very deterministic mechanism for lock management, as opposed to say
how
> > shared_ptr works.
>
> shared_ptr is completely deterministic, too. If you pay attention, you'll
> never go wrong. The problem is that it's more flexible, and a shared lock
> would probably be easier to misuse if you don't pay attention.

That depends on the definition of deterministic. Seriously, I understand
that technically you can evaluate an entire program and determine precisely
when a shared_ptr will reclaim the memory, but there are two problems with
this:

1) Evaluating an entire application to determine when the collection will
occur is problematic for resources such as mutex locks that *must* be
reclaimed in very rigid time frames.
2) You can't always determine when a "reference" is maintained to the
resource when you don't have complete access to the entire application's
source code (such as when libraries may retain a reference).

With memory collection neither of these points are very important (usually),
but with a mutex lock it can become problematic.

> > If you know the rules of move semantics this isn't a case where you'd
lose
> > the lock but didn't expect to. The problem is, as with auto_ptr,
newbies
> > don't understand the rules of move semantics so *they* are surprised.
>
> The problem is that, at present, we don't have a language construct with
> which to express move semantics. std::auto_ptr uses pass by value, and
this
> is what confuses newbies and veterans alike. Pass by value is not supposed
> to alter the original.

I agree. But would the syntax I gave earlier make the concept clearer. Let
me try to codify everything here (doing this on the fly in e-mail, so
there's bound to be numerous mistakes/bugs but it should clarify what I'm
suggesting better then I did before):

template <typename Mutex>
class lock_transfer_proxy
{
public:
   lock_transfer_proxy(Mutex* mutex, bool locked) : m_mutex(mutex),
m_locked(locked) { }
   lock_transfer_proxy(lock_transfer_proxy& proxy)
      : m_mutex(proxy.get_mutex), m_locked(proxy.release_lock()) { }
   ~lock_transfer_proxy() { if (m_mutex != 0 && m_locked)
lock_ops<Mutex>::unlock(*mutex); }
   Mutex* get_mutex() { return m_mutex; }
   bool release_lock() { bool locked = m_locked; m_locked = false; return
locked; }

private:
   Mutex* m_mutex;
   bool m_locked;
};

template <typename Mutex>
class scoped_lock : private noncopyable
{
public:
   typedef Mutex mutex_type;

   explicit scoped_lock(Mutex& mutex, bool initially_locked=true)
      : m_mutex(&mutex), m_locked(false)
   {
       if (initially_locked) lock();
   }
   explicit scoped_lock(lock_transfer_proxy<Mutex>& proxy)
      : m_mutex(proxy.get_mutex()), m_locked(proxy.release_lock())
   {
   }
   ~scoped_lock() { if (m_mutex != 0 && m_locked) unlock(); }

   void lock()
   {
      if (m_mutex == 0 || m_locked) throw lock_error();
      lock_ops<Mutex>::lock(*m_mutex);
      m_locked = true;
   }
   void unlock()
   {
       if (m_mutex == 0 || !m_locked) throw lock_error();
       lock_ops<Mutex>::unlock(*m_mutex);
       m_locked = false;
   }

   bool locked() const { return m_locked; }
   operator const void*() const { return m_locked ? this : 0; }

   lock_transfer_proxy<Mutex> transfer()
   {
      lock_transfer_proxy<Mutex> proxy(m_mutex, m_locked);
      m_mutex = 0;
      m_locked = false;
      return proxy;
   }
   // This may or may not be useful and is analogous to operator= with
auto_ptr.
   void acquire(lock_transfer_proxy& proxy)
   {
        if (m_mutex != 0 && m_locked) unlock();
        m_mutex = proxy.get_mutex();
        m_locked = proxy.release_lock();
   }

private:
   Mutex* m_mutex;
   bool m_locked;
};

Usage:

boost::mutex mutex;

boost::mutex::scoped_lock transfer_lock_out()
{
   boost::mutex::scoped_lock lock(mutex);
   return lock.transfer();
}

void acquire_transfer_from_other_scope()
{
   boost::mutex::scoped_lock lock(transfer_lock_out());
}

> Do we need to transfer locks into functions? Perhaps locks need an even
more
> restricted version of 'move semantics', that supports return by value
only.

I don't know. Assuming we don't, what kind of sophisticated implementation
would allow for return by value only?

Bill Kempf


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