|
Boost : |
From: Howard Hinnant (hinnant_at_[hidden])
Date: 2007-03-22 13:53:04
On Mar 22, 2007, at 1:14 PM, Yuval Ronen wrote:
>
>> The usual
>> idiom is to have the mutex at namespace scope, or as a class data
>> member, and have the lock referring to that mutex "on the stack". So
>> for the usual idioms, non-copyable, non-movable mutexes do not seem
>> overly problematic.
>
> I'm not sure it's not problematic. If the mutex is class data member
> (a
> very common scenario - perhaps the most common one), then it makes
> that
> class non-movable. Non-copyable - I understand, but non-movable -
> that's
> a major limitation, IMO.
I think we're ok here, both for copying and moving. Here's a
prototype class A which owns an int on the heap. And because I think
this is a cool use of the read/write locks from N2094 I'm going to use
them to implement the copy assignment. :-)
I'm typing this code in untested, so please forgive me any careless
mistakes.
class A
{
int* the_data_;
std::sharable_mutex mut_;
typedef std::sharable_lock<std::sharable_mutex> read_lock;
typedef std::exclusive_lock<std::sharable_mutex> write_lock;
public:
explicit A(int data) : the_data_(new int(data)) {}
~A() {delete the_data_;}
// copy semantics
A(const A& a) : the_data_(0)
{
read_lock lk(a.mut_);
if (a.the_data_ != 0)
the_data_ = new int(*a.the_data_);
}
A& operator=(const A& a)
{
if (this != &a)
{
read_lock lock_a(a.mut_, std::defer_lock);
write_lock lock_this(mut_, std::defer_lock);
std::lock(lock_this, lock_a); // prevents deadlock
int* new_data = a.the_data_ ? new int(*a.the_data_) :
(int*)0;
delete the_data_;
the_data_ = new_data;
}
return *this;
}
// move semantics
A(A&& a) : the_data_(a.the_data_)
{
a.the_data_ = 0; // no locking required, a is an rvalue! :-)
}
A& operator=(A&& a)
{
write_lock lock_this(mut_);
delete the_data_;
the_data_ = a.the_data_;
a.the_data_ = 0;
return *this;
}
};
I.e. you just move or copy the protected data and not the mutex.
The cool part here (imho) is the template <class Lock1, class Lock2>
void std::lock(Lock1&,Lock2&) function. You don't want to get into a
situation like this without deadlock protection:
A a1, a2;
Thread 1 Thread 2
a1 = a2; a2 = a1;
std::lock will do whatever it needs to do to lock both locks without
deadlock (personally I'm a fan of the "try and back off" algorithm,
but locking in a predefined order is also popular). And std::lock can
work with a heterogenous list of locks. :-)
-Howard
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk