C++ 通过复制和交换与两个锁进行分配

C++ 通过复制和交换与两个锁进行分配,c++,c++11,mutex,copy-and-swap,C++,C++11,Mutex,Copy And Swap,借用并修改它以使用复制和交换,这个op=线程安全吗 struct A { A() = default; A(A const &x); // Assume implements correct locking and copying. A& operator=(A x) { std::lock_guard<std::mutex> lock_data (_mut); using std::swap; swap(_data, x._d

借用并修改它以使用复制和交换,这个op=线程安全吗

struct A {
  A() = default;
  A(A const &x);  // Assume implements correct locking and copying.

  A& operator=(A x) {
    std::lock_guard<std::mutex> lock_data (_mut);
    using std::swap;
    swap(_data, x._data);
    return *this;
  }

private:
  mutable std::mutex _mut;
  std::vector<double> _data;
};
结构A{ A()=默认值; A(A const&x);//假定实现了正确的锁定和复制。 A运算符=(A x)(&O){ std::锁定保护锁定数据(_mut); 使用std::swap; 交换(_数据,x._数据); 归还*这个; } 私人: 可变std::mutex _mut; std::vector_数据; }; 我相信这个线程是安全的(记住op='s参数是通过值传递的),我能找到的唯一问题是一个隐藏在地毯下的问题:复制ctor。然而,这将是一个罕见的类,它允许复制分配,但不允许复制构造,所以问题在两个备选方案中都同样存在

鉴于自赋值非常罕见(至少在这个例子中),如果发生这种情况,我不介意额外的拷贝,考虑一下这个潜在的优化!rhs要么微不足道,要么悲观。与最初的策略(如下)相比,是否有任何其他理由选择或避免该策略

A&operator=(A const&rhs){
如果(此!=&rhs){
std::unique_lock lhs_lock(_mut,std::defer_lock);
标准::唯一锁定rhs锁定(rhs.\U mut,标准::延迟锁定);
标准:锁(左锁、右锁);
_数据=rhs.\u数据;
}
归还*这个;
}

顺便说一句,我认为这可以简洁地处理copy-ctor,至少对于这个类来说,即使它有点迟钝:

A(A const &x) : _data {(std::lock_guard<std::mutex>(x._mut), x._data)} {}
A(常数&x):_数据{(std::lock_-guard(x.。_-mut),x._数据)}{

我相信你的作业是线程安全的(当然,假设课堂外没有参考资料)。相对于
常量A&
变量,它的性能可能取决于A。我认为对于许多A来说,您的重写即使不快,也会同样快。我举的一个大反例是std::vector(以及类似的类)

vector的容量不参与其值。如果lhs相对于rhs有足够的容量,那么重用该容量,而不是将其扔掉给临时服务器,可以获得性能上的胜利

例如:

std::vector<int> v1(5);
std::vector<int> v2(4);
...
v1 = v2;
标准:向量v1(5); std::载体v2(4); ... v1=v2; 在上面的示例中,如果v1保持其执行分配的容量,则可以在不进行堆分配或释放的情况下完成分配。但如果vector使用swap习惯用法,那么它会执行一次分配和一次解除分配


我注意到,就线程安全而言,两种算法都锁定/解锁两个锁。虽然swap变量避免了同时锁定这两个变量的需要。我相信平均而言,同时锁定两者的成本很小。但在激烈争论的用例中,这可能会成为一个问题。

促使我提出这个问题。作为链接答案的原始作者,并且做出了相同的决定(使用传递值以避免双重锁定),我觉得我必须说明这两种方法之间的关键区别,这恰好太长,无法发表评论,因此,我正在编辑答案。如果复制赋值运算符的参数是右值,则复制仍然可以省略。而且它仍然是线程安全的!:-)当您有一个对右值的引用时,您可以确信没有其他线程,甚至没有其他线程,有对同一右值的引用。因此,无论你想做什么,都可以安全地使用该右值。我所说的一个例外是,如果有人将左值转换为右值。但在这种情况下,即使在多线程环境中,施法者也有责任确保程序能够真正将施法的左值视为右值。@7vies:您的结论部分正确。由每一类的作者决定拷贝分配的最佳算法。有时是复制构造和交换,有时不是。向量类成员的存在可能会影响这一决定,但也可能不会。它将取决于所有成员/基础,也可能取决于复制分配操作符最可能的用例。无法绕过性能测试。:-)我懂了。。我记得复制和交换有时像银弹一样出现,现在我知道这实际上取决于。再次感谢@7VIE:虽然性能可能很重要,但我通常将分配实现为复制和交换,因为这样更简单。因为这是一个实现细节,我可以随时回来修改它,但同时至少我的代码是正确的。@DavidRodríguez dribeas:复制和交换不也定义“当一致性发生时”?也就是说,赋值“开始”时的值(因为我们没有将赋值视为原子)。在这两种情况下,您都无法确保,例如:
a=b;断言(a==b)
std::vector<int> v1(5);
std::vector<int> v2(4);
...
v1 = v2;