为什么 parent 的方法在 child 被破坏后仍然存在
Why are the parent's methods still alive after child's destruction
我不明白为什么 Parent class 的“'execute'”函数是 运行。我觉得有两种情况:一种是 parent class,一种是 child class,但为什么呢?事实上,这个程序正在打印“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;
}
您的程序有未定义的行为,这意味着 "everything can happen"。
您启动了一个新线程,其中包含指向对象的指针 ( this )。该线程稍后将调用一个虚方法,这意味着它需要使用它指向的对象中的数据。 vtable指针本身就是class的某种数据。因为您从另一个线程删除了您的对象,所以指针 ( this ) 只是指向一个已破坏的对象,并且从已删除的对象访问数据 ( vtable ) 是未定义的行为。
您的观察取决于编译器的实现,也可能取决于优化级别。有可能,您的编译器在解构期间将 vtable 指针倒回基 class 指针。由于对象的内存未被任何其他内容覆盖(甚至未定义!),您可以在销毁后观察到对基函数的调用。但这不是你可以依赖的,因为如果你使用对象的数据成员,这里是 vtable 指针,那么在销毁后根本不允许使用任何对象。
简而言之:您的代码包含一个错误,一切都可能发生,因为它是未定义的行为。
这与线程无关。您可以同步重现整个事情 - 包括未定义的行为。
Single-threaded 版本的 类:
#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;
}
};
和 single-threaded 测试用例展示了完全相同的行为:
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
由于线程创建和对象销毁 Child
之间的竞争条件,您的代码表现出未定义的行为(在您的情况下导致 Parent::execute
调用)。要修复它,您可以在 Parent
class 中定义适当的启动和停止方法,并在 Child
析构函数中调用 stop
以防止它在线程连接之前被销毁。
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
我不明白为什么 Parent class 的“'execute'”函数是 运行。我觉得有两种情况:一种是 parent class,一种是 child class,但为什么呢?事实上,这个程序正在打印“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;
}
您的程序有未定义的行为,这意味着 "everything can happen"。
您启动了一个新线程,其中包含指向对象的指针 ( this )。该线程稍后将调用一个虚方法,这意味着它需要使用它指向的对象中的数据。 vtable指针本身就是class的某种数据。因为您从另一个线程删除了您的对象,所以指针 ( this ) 只是指向一个已破坏的对象,并且从已删除的对象访问数据 ( vtable ) 是未定义的行为。
您的观察取决于编译器的实现,也可能取决于优化级别。有可能,您的编译器在解构期间将 vtable 指针倒回基 class 指针。由于对象的内存未被任何其他内容覆盖(甚至未定义!),您可以在销毁后观察到对基函数的调用。但这不是你可以依赖的,因为如果你使用对象的数据成员,这里是 vtable 指针,那么在销毁后根本不允许使用任何对象。
简而言之:您的代码包含一个错误,一切都可能发生,因为它是未定义的行为。
这与线程无关。您可以同步重现整个事情 - 包括未定义的行为。
Single-threaded 版本的 类:
#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;
}
};
和 single-threaded 测试用例展示了完全相同的行为:
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
由于线程创建和对象销毁 Child
之间的竞争条件,您的代码表现出未定义的行为(在您的情况下导致 Parent::execute
调用)。要修复它,您可以在 Parent
class 中定义适当的启动和停止方法,并在 Child
析构函数中调用 stop
以防止它在线程连接之前被销毁。
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