Boost logo

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