依赖于初始化的顺序

Relying on order of initialisation

本文关键字:顺序 初始化 依赖于      更新时间:2023-10-16

根据c++ 14标准,非静态成员变量是按照它们在类中声明的顺序初始化的。下面的精简代码依赖于该规则来控制线程函数。

class foo
{
     foo(): 
          keep_going{true},
          my_thread(&foo::go,this)
     {}
      void go()
      {
          while(keep_going)
             check a std::condition_variable and do some work;
      }
      bool keep_going;
      std::thread my_thread;
}

注意,keep_going在线程对象之前声明,并且应该在线程进入go函数时设置为true。这很好,似乎工作正常。

然而,这是多线程代码,它是值得多疑的,所以我有两个问题:

1像这样依赖于初始化顺序安全吗?如果没有处理线程,我的实际对象就没有意义,所以我想在构造函数中设置它。

当代码依赖于相对模糊的东西(如初始化顺序)时,将代码交给他人是否不安全?
  1. 符合标准是安全的

  2. 非常不安全。很少有人意识到这一点,维护您的头文件的人可能会重新排序成员,造成灾难性的后果。

我可不相信。

虽然按照标准是安全的,但我不会这么做。

轶事:我在Windows操作系统上使用visual studio 2013编写了一个定制的ThreadPool。我声明线程池是全局的。当然,根据标准,全局对象在main返回后被析构。线程池析构器试图join每个线程,但唉!一个死锁。(你可以在这里阅读这个问题:std::thread::join()挂起,如果在使用VS2012 RC时main()退出后调用)。标准非常清楚地说明,如果一个线程是可接合的,那么连接它是没有问题的,但是正如您所看到的,这并没有完美地实现。

为什么我要告诉你这个无关的问题?因为即使是编译器和平台也有一些bug。微妙的事情可能不会在最初几个相关的支持编译器版本中100%正确地实现。

这就是我不同意这个想法的原因。作为解决方法,我将声明用std::unique_ptr包装的线程,并在构造函数体中初始化它。这样,它就不可能在keep_going之前被初始化。

foo(): 
  keep_going{true}
   { my_thread = std::make_unique<std::thread>(&foo::go,this); }

我想重写代码,使代码即使对猴子也很明显。

当类foo是基类时,可能会出现另一个潜在的问题。线程将在未完全构造的对象上启动。如果派生类的构造函数失败会发生什么?在这种情况下,最好将线程执行从构造函数移到start()方法。