为啥将 std::mutex 引入成员类会产生此编译错误?

Posted

技术标签:

【中文标题】为啥将 std::mutex 引入成员类会产生此编译错误?【英文标题】:Why does introducing std::mutex to member class generate this compile error?为什么将 std::mutex 引入成员类会产生此编译错误? 【发布时间】:2021-01-29 01:33:00 【问题描述】:

在下面的代码中,class B 包含一个成员类数组 class AB::A 有一个成员 bool 和一个成员 std::thread。 下面的代码编译得很好:

// main.cpp
#include <mutex>
#include <thread>

class B 
public:
  B();

private:

  class A 
    public:
      A( const bool& b ) : b_( b ) 

      bool b_;
      std::thread thread_;
   a_[2];
;

B::B() : a_  false ,  false    

int main( int argc, char* argv[] ) 
  B b;

  return 0;

$ g++ --version && g++ -g ./main.cpp
g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$

为什么将 std::mutex 引入B::A 会引入以下编译错误?

// main.cpp
#include <mutex>
#include <thread>

class B 
public:
  B();

private:

  class A 
    public:
      A( const bool& b ) : b_( b ) 

      bool b_;
      std::mutex mutex_;  // I break compilation!
      std::thread thread_;
   a_[2];
;

B::B() : a_  false ,  false    

int main( int argc, char* argv[] ) 
  B b;

  return 0;

$ g++ -g ./main.cpp
./main.cpp: In constructor ‘B::B()’:
./main.cpp:21:35: error: use of deleted function ‘B::A::A(B::A&&)’
 B::B() : a_  false ,  false    
                                   ^
./main.cpp:11:9: note: ‘B::A::A(B::A&&)’ is implicitly deleted because the default definition would be ill-formed:
   class A 
         ^
./main.cpp:11:9: error: use of deleted function ‘std::mutex::mutex(const std::mutex&)’
In file included from /usr/include/c++/6/mutex:44:0,
                 from ./main.cpp:2:
/usr/include/c++/6/bits/std_mutex.h:97:5: note: declared here
     mutex(const mutex&) = delete;
     ^~~~~

如果我正确理解了编译错误,它会抱怨如果没有明确构造 B::A::mutex_,就无法创建 B::A 的实例。但如果这是真的,我不明白为什么必须这样做:std::mutex 有一个默认构造函数,所以不需要任何构造函数参数,如下所示:

// main.cpp
#include <mutex>

int main( int argc, char* argv[] ) 
  std::mutex mutex[10];

  return 0;

请帮助我了解上述编译错误的性质,以及适当的修复方法。


更新:@Jarod42 和@chris 似乎发现这是一个编译器错误。我正在更新这个问题,询问是否有人可以解释这个错误的性质——初始化成员数组对象元素似乎是一件简单而基础的事情。什么类型的对象会触发此错误,为什么?我无法想象这可能是一个普遍/容易重现的问题......?


更新: 一个不太好的解决方法似乎是将B::A::A 设为空构造函数并使用右值初始化B::A::b_。 :(

// main.cpp
#include <mutex>
#include <thread>

class B 
public:
  B();

private:

  class A 
    public:
      A() : b_( false ) 

      bool b_;
      std::mutex mutex_;
      std::thread thread_;
   a_[2];
;

B::B()  

int main( int argc, char* argv[] ) 
  B b;

  return 0;

$ g++ -g ./main.cpp
$

【问题讨论】:

它构建在当前版本的 GCC 和 Clang 中。 当clang/msvc/gcc_trunk 接受它时,似乎确实是主干中修复的gcc 错误:Demo。 @StoneThrow,大概他们使用的是足够旧的版本。我使用 Godbolt 测试每个主干(以及 GCC 6.3),但它有足够的选择,您可以确定哪个版本修复了这个问题。 @RetiredNinja - 同意:我的评估也是如此。 chris 和 Jarod42 似乎已将其识别为编译器错误,我倾向于同意,因为毕竟,我实际上并没有在这里 复制 互斥锁 - 我正在初始化数组元素 就地. 要尝试解决该错误,请尝试将 std::mutex 替换为 std::unique_ptr<:mutex>。将互斥锁从类中取出,但方便访问。 【参考方案1】:

该错误的明显可能原因是复制初始化和复制列表初始化之间的细微差别:

struct A 
  A(int);
  A(A&&)=delete;
 a=1,         // error: not movable
  b=A(1),      // error
  c=1,       // OK, no temporary constructed
  d[]=1,     // error
  e[]=A1,  // error
  f[]=1;   // OK (the compiler bug)

这里一个裸的1转换为一个不能被复制/移动的临时A,而1被用来初始化终极@987654325 @尽管有“复制”一词。这种区别在 C++17 中消失了,在 C++17 中,从纯右值初始化(如转换后的 ba调用纯右值的构造函数(“强制复制省略”)。

这个问题在 C++11 之前也不会出现,因为非静态数组成员只能被默认或值初始化。 (= 当时也被认为是正常的复制初始化,根本不适用于类对象。)这就是您的解决方法有效的原因,新初始化程序的逐渐采用可能是为什么编译器错误一直存在。

请注意,尽管错误提及 std::mutex 的已删除 copy 构造函数,但重要的是它的不可可移动性(如 B::A::A(B::A&amp;&amp;) 所示) ,这就是它与std::thread 的不同之处。

【讨论】:

以上是关于为啥将 std::mutex 引入成员类会产生此编译错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 C++ 中 std::mutex 的构造函数不抛出?

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

为啥使用 std::mutex 的函数对 pthread_key_create 的地址进行空检查?

为啥 std::mutex 在带有 WIndows SOCKET 的结构中使用时会创建 C2248?

基于std::mutex std::lock_guard std::condition_variable 和std::async实现的简单同步队列

C++-mutex(待验证)