为什么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);
}
任何一个根本原因让它可以移动是个坏主意吗?
答案 0 :(得分:16)
lock_guard
始终订婚;它始终包含对互斥锁的引用,并始终在其析构函数中解锁它。如果它是可移动的,则需要保持指针而不是引用,并在其析构函数中测试指针。这看起来似乎是微不足道的代价,但是C ++哲学是你不为你不使用的东西买单。
如果您想要一个可移动(和可释放)的锁,您可以使用unique_lock
。
您可能对n3602 Template parameter deduction for constructors感兴趣,因此无需make_
个功能。它不会在C ++ 14中,但我们希望C ++ 17。
答案 1 :(得分:10)
你可以这样做:
auto&& g = std::lock_guard<std::mutex> { mutex };
显然这并不完全令人满意,因为这不会扣除。除了你需要使用list-initialization来返回一个不可移动的对象之外,你在推理工厂的尝试几乎存在:
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 };
执行列表初始化一个临时的 - 然后我们无法进入返回值。)