
Michael Glassford wrote:
I believe there was (nearly?) consensus that we want to make locks movable at some point. My conclusion was that at that point this syntax becomes possible, too. I think Peter said or implied much the same thing, maybe before I did.
I'm just going to be the voice of dissent here, again, and argue that the scoped_locks, at least, should not be moveable, otherwise they're not very scoped. The scoped lock serves a specific purpose in ensuring that the mutex doesn't remain locked outside of block scope, just like scoped_ptr ensures that a heap object has a specific lifetime. If there's enough hew and cry for a movable lock, then we should create a new class for it. That said, most of the arguments I could find on the list archive for it are to support syntax changes in initializing a scoped lock. I also saw an argument for storing locks in containers, which I can't quite fathom the need for, but I welcome the use cases. As a compromise, we could create lock_transfer<> objects that wouldn't lock a mutex, but would allow initialization syntax in places where the language makes it difficult (this is just a quick sketch, there may be errors): template <typename Mutex> class lock_transfer; template <typename Mutex> class try_lock_transfer; template <typename Mutex> lock_transfer<Mutex> lock( Mutex & m ); template <typename Mutex> try_lock_transfer<Mutex> try_lock( Mutex & m ); template <typename Mutex> class lock_transfer { private: friend class scoped_lock<Mutex>; friend class scoped_try_lock<Mutex>; friend lock_transfer<Mutex> lock<>( Mutex & m ); Mutex & m_; explicit lock_tranfer( Mutex & m ) : m_( m ) { } }; template <typename Mutex> lock_transfer<Mutex> lock( Mutex & m ) { return lock_transfer<Mutex>( m ); } template <typename Mutex> class try_lock_transfer { private: friend class scoped_try_lock<Mutex>; friend try_lock_transfer<Mutex> try_lock<>( Mutex & m ); Mutex & m_; explicit try_lock_tranfer( Mutex & m ) : m_( m ) { } }; template <typename Mutex> try_lock_transfer<Mutex> try_lock( Mutex & m ) { return try_lock_transfer<Mutex>( m ); } template <typename Mutex> class scoped_lock { Mutex & m_; public: explicit scoped_lock( Mutex & m ) : m_( m ) { lock(); } // blocking lock transfer explicit scoped_lock( lock_transfer<Mutex> const & t ) : m_( t.m_ ) { lock(); } scoped_lock & operator=( lock_tranfer<Mutex> const & t ) : m_( t.m_ ) { lock(); return *this; } operator /* convertible to bool */() const { return locked(); } }; template <typename Mutex> class scoped_try_lock { Mutex & m_; public: explicit scoped_try_lock( Mutex & m ) : m_( m ) { try_lock(); } // blocking lock transfer explicit scoped_lock( lock_transfer<Mutex> const & t ) : m_( t.m_ ) { lock(); } scoped_lock & operator=( lock_tranfer<Mutex> const & t ) : m_( t.m_ ) { lock(); return *this; } // try lock transfer explicit scoped_lock( try_lock_transfer<Mutex> const & t ) : m_( t.m_ ) { try_lock(); } scoped_lock & operator=( try_lock_tranfer<Mutex> const & t ) : m_( t.m_ ) { try_lock(); return *this; } operator /* convertible to bool */() const { return locked(); } }; void foo() { mutex m; try_mutex try_m; if ( mutex::scoped_lock l = lock( m ) ) // always true { // do stuff } if ( mutex::scoped_lock l = try_lock( m ) ) // compile error! { // never reached } if ( try_mutex::scoped_try_lock l = lock( m ) ) // always true { // do stuff } if ( try_mutex::scoped_try_lock l = try_lock( m ) ) // may be false { // do stuff } } Comments and critique welcome, Christopher -- Christopher Currie <codemonkey@gmail.com>