用std::互斥对象复制类

Copy Class with std::mutex

本文关键字:复制 对象 std      更新时间:2023-10-16

我有一个std::互斥锁作为成员的类。我正在尝试创建一个这样的类数组

class C
{
 int x;
 std::mutex m;
};
int main()
{
  C c[10];
  //later trying to create a temp C
  C temp = c[0];
}

显然以上是不可能的,因为互斥对象是不可复制的。解决这个问题的方法是通过复制构造函数。

然而,我在创建复制构造函数时有问题。我试过了

C (const C &c)
{
   x = c.x;
   //1. m
   //2. m()
   //3. m = c.m
}

我不确定这3个选择中的正确语法是什么。请帮助。

你不应该写这些行。复制构造函数的实现相当于:

C (const C &c) : x(), m()
{
   x = c.x;
}

因此互斥锁m的新实例是默认初始化的,这意味着将调用一个默认构造函数。可以安全使用。

然而,这段代码有几个值得注意的地方。例如,如果m保护x,你应该在访问value: 之前显式地锁定它。
C (const C &c)
{
    std::lock_guard<std::mutex> guard(c.m);
    x = c.x;
}

需要将m声明为可变的(因为c是复制函数中的const引用)。

mutable std::mutex m;

最后,您可以看到复制内部带有互斥锁的对象是令人困惑的,如果C是公共类,它会使它的用户感到困惑,因此在实现复制它之前请三思。

简短的回答你不能复制互斥锁

让我们从基础开始,互斥锁是互斥的简称,也就是说,当有多个线程时,你不希望它们并行地改变/修改值。您需要序列化访问或修改/read,以便值read是有效的。

在上面的例子中,你是在向变量复制一个新值。在这种情况下,您不需要在创建新对象时使用互斥锁。

您可以使用shared_ptr<C>的数组,然后您不需要C本身是可复制的…

std::互斥体m不需要复制。您可以使用默认构造的互斥锁

正如在其他答案中所述,只有在相当小的情况下才需要这样做,但是如果您有一些内部使用互斥锁的对象类,则需要使用复制和移动构造函数来显式声明除了互斥锁之外的所有内容来移动和复制。这将导致互斥锁(以及其他任何被遗漏的东西)被默认构造(即每个新的或复制的对象将获得自己唯一的互斥锁)。请确保在使用copy或move构造函数时,无论互斥锁用于保护什么,都不会被调用,因为它们不会(不能?)调用互斥锁进行锁定。

这里有一个完整的例子来帮助任何人在未来遇到这个问题:

class Shape
{
public:
    Shape() {} //default constructor
    Shape(double _size) //overloaded constructor
    {
        size = _size;
    }
    Shape(const Shape& obj) //copy constructor (must be explicitly declared if class has non-copyable member)
    {
        //Do not put any non-copyable member variables in here (ie the mutex), as they will be
        //default initialized if left out
        size = obj.size; //any variables you want to retain in the copy
    }
    Shape& operator=(const Shape&& obj) //move constructor (must be explicitly declared if class has non-copyable member)
    {
        //Do not put any non-copyable member variables in here (ie the mutex), as they will be
        //default initialized if left out
        size = obj.size;//any variables you want to retain in the move
        return *this;
    }
    double testMe() { return size; }
private:
    std::mutex dataMutex;
    double size;
};

以上都是不好的建议,建议你打破零规则。

一个更好的方法是创建一个实用程序类,根据您想要应用的规则来处理复制互斥锁的问题。例如(这可能不符合您的需求)以下代码

https://godbolt.org/z/Y86jscd6K

#include <iostream>
#include <variant>
#include <mutex>
struct mutex_holder {
    std::mutex mutex;
    mutex_holder():mutex(){}
    mutex_holder(mutex_holder const & other):mutex(){}
};

演示了一个名为mutex_holder的类型,它遵循复制时总是获得一个新的互斥锁的规则。您可以将此策略应用于需要复制

的类中的互斥锁。
struct A {
    mutex_holder mHolder;
    int x;
    int z;
    int y;
};

,然后当你需要使用锁

void Foo(A & a)
{
    std::scoped_lock(a.mHolder.mutex);
    a.x = a.z + a.y;
}

您可以看到类A是可复制和可移动的,而无需编写任何自定义的特殊成员函数构造函数。

int main(){
   A a;
   A aa = a;
   Foo(a);
   Foo(aa);
   A aaa = std::move(aa);
   Foo(aaa);
}

如果你需要在复制过程中保护一些成员,你也可以实现这样的策略类。

#include <iostream>
#include <variant>
#include <mutex>
template <typename T>
struct mutex_holder {
    std::mutex mutex;
    mutex_holder():mutex(){}
    mutex_holder(mutex_holder const & other):mutex()
    {
        std::scoped_lock lock(mutex);
        {
            data = other.data;
        }
    }
    T data;
};

struct A {
    struct Inner {
        int x;
        int z;
        int y;
    };
    mutex_holder<Inner> mHolder;
};
void Foo(A & a)
{
    std::scoped_lock(a.mHolder.mutex);
    a.mHolder.data.x = a.mHolder.data.z + a.mHolder.data.y;
}

int main(){
   A a;
   A aa = a;
   Foo(a);
   Foo(aa);
}
同样,您的业务逻辑类不应该具有自定义SMF。