
On Jul 16, 2004, at 1:41 PM, Christopher Currie wrote:
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.
The first practical application of move semantics to locks that I saw was when I started playing around with read/write/upgradable locks. The very idea of an upgradable_read_lock is that it can transfer ownership to a write_lock. And if you cast this operation in the move syntax suggested by n1377 you can get some fairly elegant code (imho). Consider a class A with a rw_mutex as a member: class A { typedef Metrowerks::rw_mutex mutex; typedef mutex::read_lock read_lock; typedef mutex::write_lock write_lock; typedef mutex::upgradable_read_lock upgradable_read_lock; public: ... void read_write_one_way(); void read_write_another_way(); ... private: mutable mutex mut_; }; There are two public functions (among others), that both read and write. The rw_mutex must be locked accordingly. For convenience, it would be nice if they could implement themselves in terms of a common private read_impl(). Something like: void A::read_write_one_way() { read_impl(); write_lock wl(mut_); // do the write } This doesn't quite work because you don't want to free the read_lock under read_impl() until you've obtained the write_lock in read_write_one_way. You could: class A { typedef Metrowerks::rw_mutex mutex; ... public: ... void read_write_one_way(); void read_write_another_way(); ... private: mutable mutex mut_; void read_impl() const; }; void A::read_impl() const { // precondition: client has at least read-locked // do the read } void A::read_write_one_way() { upgradable_read_lock url(mut_); read_impl(); write_lock wl(url); // transfer ownership // do the write } void A::read_write_another_way() { upgradable_read_lock url(mut_); read_impl(); write_lock wl(url); // transfer ownership // do the write } But if we really had move semantics as in n1377, I'd be tempted to: class A { typedef Metrowerks::rw_mutex mutex; ... public: ... void read_write_one_way(); void read_write_another_way(); ... private: mutable mutex mut_; upgradable_read_lock read_impl() const; }; A::upgradable_read_lock A::read_impl() const { // process some stuff before needing to read-lock upgradable_read_lock url(mut_); // do the read return url; } void A::read_write_one_way() { write_lock wl(read_impl()); // transfer ownership // do the write } void A::read_write_another_way() { write_lock wl(read_impl()); // transfer ownership // do the write } The functionality is the same with either coding, but in the latter I can put the read-locking logic where it belongs (in read_impl) instead of depending upon each read_write function to correctly do the read-locking. It also allows the possibility that read_impl() may not need to read-lock right at the top of its function, but perhaps part way through, resulting in higher concurrency (the less locked the better). <shrug> I don't see this as a huge win. But it does seem like a reasonable application for moving locks. If you were to tell me this scenario would be pretty rare, I'd have to agree with you. I imagine 90% of all lock use would be a simple scoped_lock (no try, no timed, no recursive, no read/write). -Howard