29

Why is std::lock_guard not movable, it would make code so much nicer:

auto locked = lock_guard(mutex);

instead of

std::lock_guard<std::mutex> locked(mutex);

Is there something wrong with creating your own version, like:

template <typename T> class lock_guard_
{
  T* Mutex_;
  lock_guard_(const lock_guard_&) = delete;
  lock_guard_& operator=(const lock_guard_&) = delete;
public:
  lock_guard_(T& mutex) : Mutex_(&mutex)
  {
    Mutex_->lock();
  }
  ~lock_guard_()
  {
    if(Mutex_!=nullptr)
      Mutex_->unlock();
  }
  lock_guard_(lock_guard_&& guard)
  {
    Mutex_ = guard.Mutex_;
    guard.Mutex_ = nullptr;
  }
};

template <typename T> lock_guard_<T> lock_guard(T& mutex)
{
  return lock_guard_<T>(mutex);
}

?

Any fundamental reason it would be a bad idea to make it movable?

Evg
  • 25,259
  • 5
  • 41
  • 83
valoh
  • 393
  • 1
  • 5
  • 6
  • Well, you do have `unique_lock`. It might just be to make the interface as simple as possible. – ecatmur Mar 19 '14 at 10:25
  • 1
    thanks, I have overlooked unique_lock :-) So I guess this answers my question: No there is no reason. Imo making it movable doesn't really make the interface more complicated but more usable and compatible with modern c++. – valoh Mar 19 '14 at 10:47
  • 1
    @valoh: If it was movable it would need to have a state where it doesn't hold a the lock. That makes it redundant, since it would offer exactly the same functionality as `unique_lock` (though I would argue that it is mostly redundant anyway). Therefore if `lock_guard` is desired as a seperate class it can't really be movable. – Grizzly Mar 19 '14 at 13:08

2 Answers2

26

lock_guard is always engaged; it always holds a reference to a mutex and always unlocks it in its destructor. If it was movable then it would need to hold a pointer instead of a reference, and test the pointer in its destructor. This might seem a trivial cost, but it is C++ philosophy that you don't pay for what you don't use.

If you want a movable (and releaseable) lock you can use unique_lock.

You might be interested in n3602 Template parameter deduction for constructors, which removes the need for make_ functions. It won't be in C++14 but we can hope for C++17.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • N3602 is [EWG issue 60](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3836.html#60). EWG is definitely in favor of the paper, but there are some issues that need ironing out. – Casey Mar 20 '14 at 00:24
14

You can do:

auto&& g = std::lock_guard<std::mutex> { mutex };

Obviously this isn’t entirely satisfactory as this performs no deduction. Your attempt at a deducing factory is almost there save for the fact that you need to use list-initialization to return a non-movable object:

template<typename Mutex>
std::lock_guard<Mutex> lock_guard(Mutex& mutex)
{
    mutex.lock();
    return { mutex, std::adopt_lock };
}

which allows for auto&& g = lock_guard(mutex);.

(The awkward dance with std::adopt_lock is due to the unary constructor being explicit. So we can’t do return { mutex }; as that’s a disallowed conversion, while return std::lock_guard<Mutex> { mutex }; performs list-initialization of a temporary — which we can’t then move into the return value.)

Luc Danton
  • 34,649
  • 6
  • 70
  • 114
  • 1
    This is nifty, slightly scary, and I might just steal it. :p – Konrad Rudolph Mar 19 '14 at 11:42
  • 2
    That's cool, but why the extra level of aggregation? It seems to work without it: https://ideone.com/KDs8qI – Vaughn Cato Mar 19 '14 at 12:33
  • @VaughnCato This may have been a misconception of mine, as the language that I thought applied only to aggregates seems to work for non-aggregates just as well. Admittedly those aren’t my favourite areas of the Standard, so I welcome any light shed on them. Thanks for the heads-up. – Luc Danton Mar 19 '14 at 13:14
  • 1
    I looked it up. According to 6.6.3p2, "A return statement with a braced-init-list initializes the object or reference to be returned from the function by copy-list-initialization (8.5.4) from the specified initializer-list.". Even though it is copy-list-initialization, that doesn't mean there is a copy made. The only thing I can see that makes copy-list-initialization special is that it doesn't allow the use of explicit constructors. Otherwise, it is regular list-initialization, and no copy is involved. – Vaughn Cato Mar 19 '14 at 13:54