根据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当代码依赖于初始化顺序等相对模糊的东西时,给别人提供代码是不安全的吗?
答案 0 :(得分:5)
根据标准是安全的。
非常不安全。很少有人知道这一点,维护你的头文件的人可能会给成员重新排序带来灾难性的后果。
我不会依赖它。
答案 1 :(得分:4)
虽然标准 是安全的,但我不会这样做。
轶事:我使用visual studio 2013在Windows操作系统上编写了一个自定义的ThreadPool。我宣布线程池是全局的。当然按标准,在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); }
答案 2 :(得分:2)
我想重写代码,使代码显而易见,甚至对猴子来说。
当类foo
是基类时,可能会出现另一个潜在的问题。线程将在非完全构造的对象上启动。如果派生类的构造函数失败会发生什么?在这种情况下,最好将构造函数中的线程执行移至start()
方法。