我不明白为什么运行Parent类的'''execute'''函数。我觉得有两个实例:一个实例用于父类,一个实例用于子类,但是为什么呢?确实,此程序正在打印“ 1 Parent”,就像我期望的是“ 1 Child”或“ 0 Parent”一样。如果我取消注释延迟线,则输出将为“ 1 Child”。
我知道此程序中存在比赛条件。编写该程序只是为了了解多线程环境中继承的工作原理。
谢谢!
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <thread>
class Parent
{
public:
std::thread myThread;
int a;
Parent() {
this->myThread = std::thread();
this->a = 0;
}
void start()
{
this->myThread = std::thread(&Parent::execute, this);
}
virtual void execute() {
std::cout << a << " Parent" << std::endl;
}
virtual ~Parent() {
while(!this->myThread.joinable());
this->myThread.join();
}
};
class Child : public Parent
{
public:
Child() {
this->a = 1;
}
void execute() override {
std::cout << a << " Child" << std::endl;
}
~Child() {
}
};
int main()
{
std::cout << "Init" << std::endl;
Child * chld = new Child();
chld->start();
//std::this_thread::sleep_for(std::chrono::milliseconds(x));
std::cout << "Delete" << std::endl;
delete chld;
return 0;
}
答案 0 :(得分:4)
您的程序具有未定义的行为,这意味着“一切皆有可能”。
您将启动一个新线程,其中包含指向对象的指针(this)。该线程稍后将调用一个虚拟方法,这意味着它需要使用其指向的对象中的数据。 vtable指针本身是该类的某种数据。由于您是从另一个线程中删除对象的,因此指针(this)只是指向已破坏的对象,而从已删除对象访问数据(vtable)是未定义的行为。
您的观察取决于编译器的实现,也可能取决于优化级别。解构期间,编译器可能会将vtable指针回退到基类指针。并且由于该对象的内存没有被任何其他内容(甚至是未定义的内容)覆盖,因此可以观察到销毁后对基本函数的调用。但这是您不能依靠的,因为如果您使用对象的数据成员(这里是vtable指针),则在销毁之后根本不允许使用任何对象。
简而言之:您的代码包含一个错误,一切都可能发生,因为它是未定义的行为。
答案 1 :(得分:2)
这与线程无关。您可以同步复制整个内容-包括未定义的行为。
您的类的单线程版本:
#include <iostream>
#include <string>
class Parent
{
public:
int a;
Parent() : a(0) {}
virtual ~Parent() {}
virtual void execute() {
std::cout << a << " Parent" << std::endl;
}
};
class Child : public Parent
{
public:
Child() {
a = 1;
}
void execute() override {
std::cout << a << " Child" << std::endl;
}
};
和单线程测试用例表现出完全相同的行为:
int main()
{
Child c;
std::cout << "=== automatic lifetime ===\n";
std::cout << "virtual dispatch: ";
c.execute();
std::cout << "explicit static dispatch: ";
c.Parent::execute();
std::cout << "=== dynamic lifetime ===\n";
Child *pc = new Child;
std::cout << "virtual dispatch: ";
pc->execute();
std::cout << "explicit static dispatch: ";
pc->Parent::execute();
std::cout << "=== undefined behaviour ===\n";
delete pc;
std::cout << "explicit static dispatch: ";
pc->Parent::execute();
std::cout << "virtual dispatch: ";
pc->execute();
}
最后两个输出语句被交换了,因为最后一个输出语句在我运行时崩溃了(倒数第二个仍然是UB,但碰巧没有崩溃)
=== automatic lifetime ===
virtual dispatch: 1 Child
explicit static dispatch: 1 Parent
=== dynamic lifetime ===
virtual dispatch: 1 Child
explicit static dispatch: 1 Parent
=== undefined behaviour ===
explicit static dispatch: 1 Parent
Segmentation fault (core dumped) ./a.out
答案 2 :(得分:2)
由于线程创建和Parent::execute
对象销毁之间的竞争状况,您的代码表现出Undefined行为(在您的情况下导致Child
调用)。要对其进行修复,可以在Parent
类中定义适当的启动和停止方法,并在stop
析构函数中调用Child
,以防止它在线程加入之前被破坏。
class Parent
{
public:
Parent(): myThread_() {
std::cout << "Parent CTor" << std::endl;
}
virtual ~Parent() = default;
bool start()
{
std::cout << "start" << std::endl;
if (myThread_.joinable()) {
std::cout << "already started" << std::endl;
return false;
}
myThread_ = std::thread([this]() {
execute();
});
return true;
}
bool stop() {
std::cout << "stop" << std::endl;
if (!myThread_.joinable()) {
std::cout << "not started" << std::endl;
return false;
}
myThread_.join();
return true;
}
virtual void execute() = 0;
private:
std::thread myThread_;
};
class Child : public Parent
{
public:
Child() {
std::cout << "Child CTor" << std::endl;
}
~Child() override {
stop();
}
void execute() override {
std::cout << "Child::execute()" << std::endl;
}
};
int main()
{
std::cout << "Init" << std::endl;
Child * chld = new Child();
chld->start();
std::cout << "Delete" << std::endl;
delete chld;
return 0;
}
我将Parent::execute
定义为抽象,因为您可能根本不希望调用它,并且在发生另一个错误的情况下,至少可以得到
terminate, pure virtual method called