为什么std :: lock_guard不可移动?

val*_*loh 24 c++ standards multithreading movable c++11

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

auto locked = lock_guard(mutex);
Run Code Online (Sandbox Code Playgroud)

代替

std::lock_guard<std::mutex> locked(mutex);
Run Code Online (Sandbox Code Playgroud)

创建自己的版本有什么问题,例如:

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);
}
Run Code Online (Sandbox Code Playgroud)

任何根本原因让它变得可移动是个坏主意吗?

eca*_*mur 20

lock_guard一直从事; 它始终包含对互斥锁的引用,并始终在其析构函数中解锁它.如果它是可移动的,则需要保持指针而不是引用,并在其析构函数中测试指针.这可能看起来是微不足道的代价,但是C++哲学是你不为你不使用的东西买单.

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

您可能对构造函数的n3602模板参数推导感兴趣,这消除了对make_函数的需求.它不会在C++ 14中,但我们希望C++ 17.


Luc*_*ton 11

你可以做:

auto&& g = std::lock_guard<std::mutex> { mutex };
Run Code Online (Sandbox Code Playgroud)

显然这并不完全令人满意,因为这不会扣除.除了你需要使用list-initialization来返回一个不可移动的对象之外,你在推理工厂的尝试几乎是存在的:

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

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

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

  • 这很酷,但是为什么会有额外的聚合?它似乎没有它工作:https://ideone.com/KDs8qI (2认同)