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

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

有没有任何根本性的原因,使得将它变成可移动的不是一个好主意?


嗯,你确实有unique_lock。这可能只是为了使接口尽可能简单。 - ecatmur
1
谢谢,我忽略了unique_lock :-) 所以我想这回答了我的问题:没有理由。在我看来,使它可移动并不会使接口更加复杂,而是更加可用和兼容现代C++。 - valoh
1
@valoh:如果它是可移动的,那么它需要有一个状态,在该状态下它不持有锁。这使它变得多余,因为它将提供与unique_lock完全相同的功能(尽管我认为它大多数情况下都是多余的)。因此,如果希望将lock_guard作为单独的类使用,它实际上不能是可移动的。 - Grizzly
2个回答

26

lock_guard 总是处于锁定状态,它始终持有一个互斥量的引用,并且总是在其析构函数中解锁。如果它可以被移动,则需要持有指针而不是引用,并在其析构函数中测试该指针。这可能看起来是微不足道的代价,但C++ 的哲学是您不必为您不使用的东西付费。

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

您可能会对n3602 模板参数推导的构造函数感兴趣,它消除了需要使用 make_ 函数的必要性。它可能不会出现在 C++14 中,但我们可以期待它在 C++17 中实现。


N3602是EWG issue 60。EWG绝对支持这篇论文,但还有一些需要解决的问题。 - Casey

14

您可以做到:

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 };则执行了一个临时对象列表初始化 - 然后我们无法将其移动到返回值中。)


1
这很巧妙,稍微有点可怕,我可能会借鉴一下。 :p - Konrad Rudolph
2
很酷,但为什么要增加额外的聚合级别?它似乎没有这个也能工作:https://ideone.com/KDs8qI - Vaughn Cato
@VaughnCato 这可能是我的一个误解,因为我认为只适用于聚合物的语言似乎同样适用于非聚合物。诚然,这些不是我最喜欢的标准领域,所以我欢迎任何对它们的解释。感谢您的提醒。 - Luc Danton
1
我查了一下。根据6.6.3p2,“具有大括号初始化列表的返回语句通过从指定的初始化程序列表进行复制列表初始化(8.5.4)来初始化将从函数返回的对象或引用。”虽然它是复制列表初始化,但这并不意味着会进行复制。我唯一能看出使复制列表初始化特殊的是它不允许使用显式构造函数。否则,它是常规的列表初始化,并且不涉及任何复制。 - Vaughn Cato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接