
Peter Dimov wrote:
Eric Niebler wrote:
I continue to believe that a better interface for a unified lock involves descriptively named helper functions:
scoped_lock l1( m ); // lock unconditionally scoped_lock l1 = defer_lock( m ); // don't lock scoped_lock l2 = try_lock( m ); // try lock scoped_lock l3 = timed_lock( m, t ); // timed lock
Maybe. Do you have a sketch of the specification? (Not the implementation.)
I know it's not particulary strong point, but I used approach propsed by Eric in the past, and it worked pretty well. I used something similar to: template <typename T> inline typename lock::scoped_lock_impl<T> acquire(lock::lock_impl<T>& lock) { return lock::scoped_lock_impl<T>(lock); } template <typename T> inline typename lock::scoped_lock_impl<T> try_acquire(lock::lock_impl<T>& lock, unsigned long timeout) { return lock::scoped_lock_impl<T>(lock, timeout); } template <typename T> inline typename lock::scoped_lock_impl<T> try_acquire(lock::lock_impl<T>& lock) { return lock::scoped_lock_impl<T>(lock, lock::scoped_lock_impl<T>::dont_wait_t()); } Where scoped_lock_impl was similar to: struct scoped_lock_t { protected: scoped_lock_t(); ~scoped_lock_t(); // safe_bool is typedef to "pointer to member of private struct" // ... public: virtual bool active() const = 0; operator safe_bool() const { return active(); } }; typedef scoped_lock_t& scoped_lock; template <typename T> class scoped_lock_impl : public scoped_lock_t { lock_impl<T>* lock_; // non-assignable scoped_lock_impl<T>& operator= (scoped_lock_impl<T>& other); public: struct dont_wait_t {}; virtual bool active() const { return lock_ != NULL; } explicit scoped_lock_impl(lock_impl<T>& lock) : lock_(&lock) { lock.acquire(); } scoped_lock_impl(lock_impl<T>& lock, unsigned long timeout) : lock_(lock.try_acquire(timeout)) {} scoped_lock_impl(lock_impl<T>& lock, const dont_wait_t&) : lock_(lock.try_acquire()) {} lock_impl<T>* release() { lock_impl<T>* tmp = lock_; lock_ = NULL; return tmp; } // cctor is passing ownership scoped_lock_impl(scoped_lock_impl& other) : lock_(other.release()) {} ~scoped_lock_impl() { if (lock_) lock_->release(); } }; and lock_impl is: template <typename T> class lock_impl { protected: lock_impl() {} ~lock_impl() {} void acquire() { (static_cast<T*> (this))->acquire(); } T* try_acquire() { return (static_cast<T*> (this))->try_acquire(); } T* try_acquire(unsigned long timeout) { return (static_cast<T*> (this))->try_acquire(timeout); } void release() { (static_cast<T*> (this))->release(); } }; and actual syncronization primitives are: class critical_section : public lock::lock_impl<critical_section> { // ... }; class mutex : public lock::lock_impl<mutex> { // ... }; Here is simple usage: { scoped_lock l1 = acquire(mymutex1_); // ok, we own mutex now } // now we do not own it { scoped_lock l2 = try_acquire(mymutex2_); if (l.active()) { // we own mutex } // we still own mutex here } or if (scoped_lock l3 = try_acquire(mymutex2_, 1000L)) { // we own mutex } If type of mymutex_ changes from mutex to critical_section, creation of l3 will no longer compile (crit. section does not support timed lock), but no other code changes are required. And compiler error will clearly point to the problem. Creation of l2 will still compile, as try_acquire with no timeout is using different constructor of scoped_lock_impl. B.