29

为什么std::lock_guard不可移动,它会使代码变得更好:

auto locked = lock_guard(mutex);

代替

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

创建自己的版本是否有问题,例如:

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);
}

?

使其可移动是一个坏主意的任何根本原因?

4

2 回答 2

25

lock_guard总是订婚;它始终持有对互斥锁的引用,并始终在其析构函数中将其解锁。如果它是可移动的,那么它需要持有一个指针而不是一个引用,并在其析构函数中测试指针。这似乎是微不足道的成本,但 C++ 的理念是,您不必为不使用的东西付费。

如果你想要一个可移动(和可释放)的锁,你可以使用unique_lock.

您可能对n3602 Template parameter deduction for constructors感兴趣,它消除了对make_函数的需求。它不会出现在 C++14 中,但我们可以期待 C++17。

于 2014-03-19T11:21:45.817 回答
13

你可以做:

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

显然,这并不完全令人满意,因为这没有扣除。除了您需要使用列表初始化来返回一个不可移动对象之外,您在推导工厂的尝试几乎就在那里:

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

这允许auto&& g = lock_guard(mutex);.

(尴尬的舞蹈std::adopt_lock是由于一元构造函数是显式的。所以我们不能这样做return { mutex };,因为这是不允许的转换,同时return std::lock_guard<Mutex> { mutex };执行临时的列表初始化 - 然后我们不能进入返回值。)

于 2014-03-19T11:38:27.497 回答