我遇到了一个奇怪的C ++代码行为,不确定它是编译器错误还是我的代码的未定义/未指定行为。这是代码:
#include <unistd.h>
#include <iostream>
#include <thread>
struct Parent {
std::thread t;
static void entry(Parent* p) {
p->init();
p->fini();
}
virtual ~Parent() { t.join(); }
void start() { t = std::thread{entry, this}; }
virtual void init() { std::cout << "Parent::init()" << std::endl; }
virtual void fini() { std::cout << "Parent::fini()" << std::endl; }
};
struct Child : public Parent {
virtual void init() override { std::cout << "Child::init()" << std::endl; }
virtual void fini() override { std::cout << "Child::fini()" << std::endl; }
};
int main() {
Child c;
c.start();
sleep(1); // <========== here is it
return 0;
}
代码的输出如下,这并不奇怪:
Child::init()
Child::fini()
但是,如果注释掉函数调用“sleep(1)”,则输出为:
Parent::init()
Parent::~fini()
在Ubuntu 15.04上测试,gcc-4.9.2和clang-3.6.0都表现出相同的行为。编译器选项:
g++/clang++ test.cpp -std=c++11 -pthread
它看起来像一个竞争条件(vtable在线程启动之前没有完全构造)。这段代码是不正确的吗?编译器错误?或者它应该是这样的?
答案 0 :(得分:4)
“线程使用子对象,但子对象在连接线程之前被销毁(因为只有在子节点被破坏后才会进行连接)。
Child
对象在main
结束时被销毁。执行Child
析构函数,并有效地调用Parent
析构函数,其中Parent
基(不是这样)和数据成员(线程对象)被销毁。当析构函数被调用到基类链时,对象的动态类型会在构造期间以相反的顺序发生变化,因此此时对象的类型为Parent
线程函数中的虚拟调用可以在调用Child
析构函数之前,重叠或之后发生,并且在重叠的情况下,有一个线程访问存储(实际上是vtable指针),即被另一个线程改变了。所以这是未定义的行为。
答案 1 :(得分:-1)
这是常见的设计问题;你试图做的是经典的反模式。
Parent
不能同时是一个线程管理器,启动一个线程并等待线程终止:
virtual ~Parent() { t.join(); }
void start() { t = std::thread{entry, this}; }
还有一个线程对象:
virtual void init() { std::cout << "Parent::init()" << std::endl; }
virtual void fini() { std::cout << "Parent::fini()" << std::endl; }
这是两个截然不同的概念,严格说不兼容。
(并且线程对象一般没用。)