【问题标题】:Relying on order of initialisation依赖于初始化的顺序
【发布时间】:2015-09-30 15:44:31
【问题描述】:

根据 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 这样依赖初始化顺序是否安全?没有处理线程,我的真实对象没有意义,所以我想在构造函数中设置它。

2 当代码依赖于相对模糊的东西(如初始化顺序)时,将代码提供给他人是否不安全?

【问题讨论】:

  • 初始化顺序并不晦涩,定义明确,这段代码没有错。
  • 1) 是的,初始化顺序是规范的一部分,2) 是 - 清晰的文档可能会有所帮助
  • 我只会依靠它并提供良好的评论解释。遵循这句话“永远像你的代码管理员一样的代码是一个知道你住在哪里的连环杀手”

标签: c++ c++14


【解决方案1】:
  1. 按照标准是安全的。

  2. 极度不安全。很少有人知道这一点,维护您的头文件的人可能会对成员进行重新排序,从而造成灾难性后果。

我不会依赖它。

【讨论】:

  • 我在头文件中加入了一些块帽警告。
【解决方案2】:

虽然按标准是安全的,但我不会去。

轶事:我使用 Visual Studio 2013 在 Windows 操作系统上编写了一个自定义的线程池。我声明线程池是全局的。当然按照标准,全局对象在 main 返回后会被销毁。线程池析构函数试图join每个线程,但是唉!死锁。 (您可以在此处阅读有关此问题的信息:std::thread::join() hangs if called after main() exits when using VS2012 RC)。标准非常明确地指出,如果一个线程是可连接的,那么连接它就没有问题,但是正如您所看到的,这并没有完美地实现。

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

这就是为什么我不会采用这个想法的原因。 作为一种解决方法,我将声明包裹在std::unique_ptr 中的线程并在构造函数主体中对其进行初始化。这样就不可能在keep_going 之前初始化它。

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2020-10-23
      • 2011-04-14
      • 2021-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多