为什么在孩子被破坏后父母的方法仍然有效

时间:2019-06-13 13:39:00

标签: c++ multithreading inheritance race-condition

我不明白为什么运行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;
}

3 个答案:

答案 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