作为类成员的 std::mutex,并将类对象存储到容器中

5
以下是最小化的代码,可以重现错误。
#include <iostream>
#include <mutex>
#include <vector>

class A {
    std::mutex mutex;
    public:
    A(){};
};
int main() 
{
    std::vector<std::pair<std::string,A>> aa;
    A a;
    //aa.push_back(std::make_pair(std::string("aa"),A()));
    //aa.push_back(std::make_pair(std::string("aa"),a));
    aa.push_back(std::make_pair(std::string("aa"),std::move(a)));    
}

以下是错误信息。

Microsoft (R) C/C++ 优化编译器 版本 19.16.27026.1,适用于 x64 版权所有 (C) Microsoft Corporation。保留所有权利。

>   C:\Program Files (x86)\Microsoft Visual
> Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include\xlocale(319):
> warning C4530: C++ exception handler used, but unwind semantics are
> not enabled. Specify /EHsc    C:\Program Files (x86)\Microsoft Visual
> Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include\utility(405):
> error C2440: '<function-style-cast>': cannot convert from 'initializer
> list' to '_Mypair'    C:\Program Files (x86)\Microsoft Visual
> Studio\2017\Community\VC\Tools\MSVC\14.16.27023\include\utility(405):
> note: No constructor could take the source type, or constructor
> overload resolution was ambiguous
>   ..\examples\json_object\json.cpp(16): note: see reference to function
> template instantiation 'std::pair<std::string,A>
> std::make_pair<std::string,A>(_Ty1 &&,_Ty2 &&)' being compiled            with
>           [
>               _Ty1=std::string,
>               _Ty2=A          ]

gcc编译器出现了相似错误。 当我从类中移除std::mutex或者不将对象推入std::vector时,就可以顺利编译。

3个回答

11

根据std::mutex文档。

std::mutex不可复制,也不可移动。

由于类A包含std::mutex变量mutex,因此它也不能移动。


3
克服这个问题的方法是使用智能指针来管理std::mutex。 - freakish

5
正如 P.W. 指出的那样,std::mutex 是既不可复制也不可移动的,这是有充分理由的。拥有互斥锁的整个意义在于保护某些数据免受同时多线程访问。移动操作本身也需要受到保护,互斥锁应该被移动操作所使用
下面的示例提供了一个带有可移动数据的类,并展示了在移动操作中应该如何使用互斥锁(复制操作会类似)。
#include <iostream>
#include <mutex>
#include <vector>
#include <memory>

class A {
public:
  A() {};

  // Move constructor
  A(A&& other) {
    std::lock_guard<std::mutex> guard(other.m_mutex);
    m_data = std::move(other.m_data);
  }

  // Move operator
  A& operator=(A&& other) {
    if (this == &other) return *this;

    // Lock this and other in a consistent order to prevent deadlock
    std::mutex* first;
    std::mutex* second;
    if (this < &other) {
      first = &this->m_mutex;
      second = &other.m_mutex;
    } else {
      first = &other.m_mutex;
      second = &this->m_mutex;
    }
    std::lock_guard<std::mutex> guard1(*first);
    std::lock_guard<std::mutex> guard2(*second);

    // Now both this and other are safe to access.  Do the actual data move.
    m_data = std::move(other.m_data);
    return *this;
  }

private:
  std::mutex m_mutex;
  std::unique_ptr<int> m_data;
};

int main() {
  std::vector<std::pair<std::string,A>> aa;
  A a1;
  A a2;
  a1 = std::move(a2);
  aa.emplace_back("aa", std::move(a1));
}

1
好答案。Howard Hinnant的这个回答展示了使用std::unique_lockstd::defer_lockstd::lock来避免死锁的更简单方法:https://dev59.com/410a5IYBdhLWcg3wva56#29988626 - Hari
1
@Hari 谢谢你提供的链接。std::lock()可以防止死锁,相比手动编写逻辑,这是一个更好的解决方案。 - undefined

2

根据P.W提供的指示和freakish给出的提示,我得出了以下解决方案。

#include <iostream>
#include <mutex>
#include <vector>
#include <memory>

class A {
    std::mutex mutex;
    public:
    A(){};
};
int main() 
{
    std::vector<std::pair<std::string,std::shared_ptr<A>>> aa;
    A a;
    //aa.push_back(std::make_pair(std::string("aa"),A()));
    //aa.push_back(std::make_pair(std::string("aa"),a));
    aa.push_back(std::make_pair(std::string("aa"),std::make_shared<A>()));   
}

我修改了容器,将智能指针存储为对象的替代。


5
对此建议,除非您真正需要共享所有权,否则应该使用std::unique_ptr。90%的情况下,您并不需要共享所有权。 - NathanOliver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接