c – 线程内的虚拟调用忽略派生类

在以下程序中,我在一个线程中有一个虚拟调用
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>

class A {
public:
    virtual ~A() { t.join(); }
    virtual void getname() { std::cout << "I am A.\n"; }
    void printname() 
    { 
        std::unique_lock<std::mutex> lock{mtx};
        cv.wait(lock,[this]() {return ready_to_print; });
        getname(); 
    };
    void set_ready() { std::lock_guard<std::mutex> lock{mtx}; ready_to_print = true; cv.notify_one(); }
    void go() { t = std::thread{&A::printname,this}; };

    bool ready_to_print{false};
    std::condition_variable cv;
    std::mutex mtx;
    std::thread t{&A::printname,this};
};

class B : public A {
public:
    int x{4};
};

class C : public B {
    void getname() override { std::cout << "I am C.\n"; }
};

int main()
{
    C c;
    A* a{&c};
    a->getname();
    a->set_ready();
}

我希望该程序能打印出来:

I am C.
I am C.

但是instead it prints

I am C.
I am A.

在程序中,我等到派生对象完全构造后再调用虚拟成员函数.但是,线程在对象完全构造之前启动.

如何确保虚拟通话?

解决方法

显示代码表现出竞争条件和未定义的行为.

在你的主():

C c;

// ...

a->set_ready();

紧接着,在set_ready()返回后,执行线程离开main().这会立即破坏c,从超类C开始,继续破坏B,然后是A.

c在自动范围内声明.这意味着只要main()返回,它就会消失.加入合唱团看不见.它不复存在了.它不复存在.这是一个前对象.

你的join()在超类的析构函数中.没有什么可以阻止C被摧毁.析构函数只会暂停并等待加入线程,当超类被破坏时,C将立即开始被销毁!

一旦C超类被销毁,其虚拟方法就不再存在,并且调用函数将最终在基类中执行虚函数.

同时另一个执行线程正在等待互斥锁和条件变量.竞争条件是你无法保证其他执行线程将在父线程销毁C之前唤醒并开始执行,它在发出条件变量信号后立即执行.

发出条件变量的所有信号都表明,无论执行线程在条件变量上旋转,执行线程都将开始执行.最终.该线程可以在一个非常负载的服务器上,在通过条件变量发出信号之后,在几秒钟后开始执行.它的目标很久以前就消失了.它在自动范围内,并且main()将其销毁(或者更确切地说,C子类已经被销毁,并且A的析构函数正在等待加入该线程).

您正在观察的行为是在收到来自条件变量的信号并解锁其互斥锁之后,在std :: thread转向进行虚拟方法调用之前管理销毁C超类的父线程.

这就是竞争条件.

此外,在销毁虚拟对象的同时执行虚拟方法调用已经是非启动性的.这是未定义的行为.即使执行线程在重写方法中结束,其对象也会被另一个线程同时销毁.无论你转向哪个方向,你都非常紧张.

经验教训:绑定std :: thread来执行此对象中的某些操作是未定义行为的雷区.有办法正确地做到这一点,但这很难.

相关文章

/** C+⬑ * 默认成员函数 原来C++类中,有6个默认成员函数: 构造函数 析构函数 拷贝...
#pragma once // 1. 设计一个不能被拷贝的类/* 解析:拷贝只会放生在两个场景中:拷贝构造函数以及赋值运...
C类型转换 C语言:显式和隐式类型转换 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译...
//异常的概念/*抛出异常后必须要捕获,否则终止程序(到最外层后会交给main管理,main的行为就是终止) try...
#pragma once /*Smart pointer 智能指针;灵巧指针 智能指针三大件//1.RAII//2.像指针一样使用//3.拷贝问...
目录&lt;future&gt;future模板类成员函数:promise类promise的使用例程:packaged_task模板类例程...