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