为什么 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