具有互斥锁成员的默认移动构造函数

Default move constructor with mutex member

本文关键字:默认 移动 构造函数 成员      更新时间:2023-10-16

我有一个删除了复制构造函数的类,我正在尝试将互斥成员放入如下所示的内容中:

struct A {
    A(const A &other) = delete;
    A& operator=(const A &other) = delete;
    A(A&& other) = default;
    A& operator=(A &&other) = default;
    std::mutex lock;
};

编译器抱怨我试图调用已删除的复制构造函数,我总结这是由于 std::mutex 类型是不可移动的。如何让互斥体成员以最小的麻烦与移动构造函数一起玩?我实际上不想将互斥成员本身移动到新构造的对象中,并且实际上希望每个移动的对象都只是构造自己的互斥锁

我实际上不想将互斥成员本身移动到新构造的对象中,并且实际上希望每个移动的对象都只是构造自己的互斥锁

然后只需定义移动构造函数即可构造新的互斥锁:

struct A {
    A(const A &other) = delete;
    A& operator=(const A &other) = delete;
    A(A&& other)
        : lock()
    {
    }
    A& operator=(A &&other) = delete;
    std::mutex lock;
};

移动分配仍然是一个问题,可能应该删除。除非你能回答这个问题:当你被分配一个新值时,现有的互斥锁成员会发生什么?特别是:如果在锁定现有互斥锁时为您分配了新值怎么办?

作为为类提供自定义移动操作的替代方法,您可以创建一个泛型包装器:

template <class T>
class PerObject
{
  T data;
public:
  PerObject() = default;
  PerObject(const PerObject&) {}
  PerObject& operator= (const PerObject&) { return *this; }
  T& operator* () const { return data; }
  T* operator-> () const { return &data; }
};

并像这样使用它:

struct A {
    A(const A &other) = delete;
    A& operator=(const A &other) = delete;
    A(A&& other) = default;
    A& operator=(A &&other) = default;
    PerObject<std::mutex> lock;
};
包装

器的复制(和移动(操作是无操作的,因此包含包装器的对象将始终包含它开始的对象。


警告:但是,根据类使用互斥锁的方式,上述情况实际上可能是危险的。如果互斥锁用于保护类中的其他数据,则在将对象分配到时,它应该可能被锁定,因此无论如何都必须提供手动移动操作。在这种情况下,代码可能如下所示:

struct A {
    A(A&& other) : lock{}, /* anything else you need to move-construct */
    {
      // Note: it might even be necessary to lock `other.lock` before moving from it, depending on your class's exact semantics and expected use.
    }
    A& operator=(A &&other)
    {
      if (this == &other) return *this;  // Otherwise, double-locking would be possible
      // If you need to lock only this object:
      std::unique_lock<std::mutex> l(lock);
      // Alternatively, if you need to lock both objects:
      std::scoped_lock l(lock, other.lock);
      // Now move data from other to this
      return *this;
    }
    std::mutex lock;
};

一种方法是使移动构造函数在调用时创建新mutex

 A(A&& other): lock()
 {
     //... move other things
 }

您还可以对std::mutex使用std::unique_ptr(),因为它是可移动的。

struct A {
    A(const A &other) = delete;
    A& operator=(const A &other) = delete;
    A(A&& other) = default;
    A& operator=(A &&other) = default;
    std::unique_ptr<std::mutex> lock;
};
A::A() : lock(new std::mutex())

使用此方法,您不会在每次移动对象时创建新的互斥锁,这将消除一些开销。