$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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