c++ 使用std::mutex复制类

xdyibdwo  于 2023-07-01  发布在  其他
关注(0)|答案(6)|浏览(330)

我有一个成员是std::mutex的类。我正在尝试创建一个数组这样的类

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个选择。请帮帮我

h6my8fg2

h6my8fg21#

你不应该写这些台词。复制构造函数的实现等效于:

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

因此,互斥体m的新示例是default initialized,这意味着将调用一个默认构造函数。它可以安全地使用。
但是,关于此代码有几个conserns。也就是说,如果m保护x,你应该在访问value之前显式地锁定它:

C (const C &c)
{
    std::lock_guard<std::mutex> guard(c.m);
    x = c.x;
}

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

mutable std::mutex m;

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

ct2axkht

ct2axkht2#

简短的回答你不复制互斥体。
让我们从基础开始,互斥是互斥的简称,即你要确保,当有多个线程时,你不希望它们并行地更改/修改值。您希望序列化访问或修改/读取,以便读取的值有效。
在上面的例子中,你复制了一个新的值到变量中。在这种情况下,你不需要使用互斥锁,因为你正在创建一个新的对象。

rdrgkggo

rdrgkggo3#

你可以使用一个shared_ptr<C>的数组,那么你就不需要C本身是可复制的。

hpxqektj

hpxqektj4#

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

rqdpfwrv

rqdpfwrv5#

正如在其他答案中所述,只有在非常小的情况下你才会想要这样做,但是如果你有一些内部使用互斥锁的对象类,你会想要制作复制和移动构造函数,显式声明除了互斥锁之外的所有东西都要移动和复制。这将导致互斥锁(以及其他任何遗漏的东西)被默认构造(即每个新的或复制的对象将获得自己唯一的互斥锁)。确保在使用复制或移动构造函数时,无论您的互斥体是用来防止被调用的,因为它们不会(不能?)调用互斥锁来锁定。
下面是一个完整的例子,以帮助任何人在未来遇到这个问题:

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

ff29svar6#

以上都是不好的建议,建议你打破rule of zero
更好的方法是创建一个实用程序类,用于根据您希望应用的规则处理复制互斥锁的问题。例如(这可能不符合您的要求)以下代码
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_保持器 * 的类型,它遵循复制时副本总是获得新互斥体的规则。您可以将此策略应用于需要复制的某个类中的互斥体

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。让编译器为您编写它们。

相关问题