我有一个以 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 个选项中正确的语法是什么。请帮忙。
您不应该写任何这些行。您的复制构造函数的实现相当于:
C (const C &c) : x(), m()
{
x = c.x;
}
因此,互斥体
m
的新实例是默认初始化的,这意味着将调用默认构造函数之一。可以安全使用。
但是,对于这段代码有一些担忧。 IE。如果
m
保护 x
,则应在访问值之前显式锁定它:
C (const C &c)
{
std::lock_guard<std::mutex> guard(c.m);
x = c.x;
}
这需要将
m
声明为可变的(因为 c
是复制构造函数中的 const 引用)。
mutable std::mutex m;
最后,您可以看到复制内部带有互斥体的对象是令人困惑的,如果
C
是公共类,它会让它的用户感到困惑,所以在实现复制之前要三思而后行。
简短的回答,你不复制互斥锁。
让我们从基础开始,互斥量是互斥的简称,即您要确保,当有多个线程时,您不希望它们并行更改/修改值。您想要序列化访问或修改/读取,以便读取的值有效。
在上述情况下,您将新值复制到变量。在这种情况下,您在创建新对象时不需要使用互斥锁。
您可以使用
shared_ptr<C>
数组,那么您就不需要 C
本身可复制...
std::mutex m 不必复制。您可以使用默认构造的互斥锁。
正如其他答案中所述,只有在相当少数的情况下您才会想要这样做,但是如果您有一些内部使用互斥体的对象类,您将需要复制并移动显式声明的构造函数除了互斥锁之外,所有要移动和复制的内容。这将导致互斥体(以及其他任何遗漏的东西)被默认构造(即每个新的或复制的对象将获得其自己唯一的互斥体)。确保在使用复制或移动构造函数时不会调用互斥体用于保护的任何内容,因为它们不会(不能?)调用互斥体进行锁定。
这是一个完整的示例,可以帮助将来遇到此问题的任何人:
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 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 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。让编译器为你编写它们。