首页 > 解决方案 > 使用共享指针来自另一个线程的纯虚拟调用

问题描述

我觉得很奇怪。请帮我解释一下。我有一个在单独的线程中启动无限循环的类,以及两个继承它的类。其中一个类实现了要在 as 外部触发的接口std::shared_ptr,而另一个类则持有该接口 as std::weak_ptr。请看下面的代码。很抱歉有很多代码,我试图尽可能短地重现错误。为什么有时我有纯虚Sender::notify函数调用?据我所知std::shared_ptr是可重入的。

#include <iostream>
#include <memory>
#include <thread>
#include <atomic>
#include <list>
#include <mutex>


class Thread : private std::thread {
    std::atomic_bool run {true};
public:
    Thread() : std::thread([this](){ thread_fun(); }) {}

    void thread_fun() {
        while (run) loop_iteration();
    }

    virtual void loop_iteration() = 0;

    virtual ~Thread() {
        run.exchange(false);
        join();
        std::cout << "Thread released." << std::endl;
    }
};

class Sender : public Thread {
public:
    class Signal{
    public:
        virtual void send() = 0;
        virtual ~Signal(){}
    };

    void add_receiver(std::weak_ptr<Signal> receiver) {
        std::lock_guard<std::mutex> lock(receivers_mutex);
        receivers.push_back(receiver);
    }

    void notify() {
        std::lock_guard<std::mutex> lock(receivers_mutex);
        for (auto r : receivers)
            if (auto shp = r.lock())
                shp->send(); //Somethimes I get the pure virtual call here
    }

private:
    std::mutex receivers_mutex;
    std::list<std::weak_ptr<Signal>> receivers;

    void loop_iteration() override {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        notify();
    }
};

class Receiver : public Thread, public Sender::Signal {
    std::atomic_bool notified {false};

public:
    void send() override {
        notified.exchange(true);
    }

private:
    void loop_iteration() override {
        std::this_thread::sleep_for(std::chrono::milliseconds(250));
        std::cout << "This thread was " << (notified? " " : "not ") << "notified" << std::endl;
    }
};


int main() {
   std::shared_ptr<Thread>
           receiver = std::make_shared<Receiver>(),
           notifier = std::make_shared<Sender>();

   std::dynamic_pointer_cast<Sender>(notifier)->add_receiver(
               std::dynamic_pointer_cast<Sender::Signal>(receiver));

   receiver.reset();

   notifier.reset();

   return 0;
}

标签: c++multithreadingshared-ptr

解决方案


在构造和销毁过程中,多态性不会像您期望的那样工作。当前类型是仍然存在的最派生类型。当你在你的对象Thread::~ThreadSender一部分已经被完全破坏时,调用它的覆盖是不安全的。

thread_fun尝试loop_iterator()在构造函数完成之前或析构函数启动之后运行时,它不会进行多态分派,而是调用Thread::loop_iteration纯虚函数(= 0)。

请参阅https://en.cppreference.com/w/cpp/language/virtual#During_construction_and_destruction

这是一个演示:https ://godbolt.org/z/4vsPGYq97

对象在derived一秒钟后被销毁,此时您会看到输出更改,表明被调用的虚函数在该点发生更改。

我不确定这段代码是否有效,或者derived在执行其成员函数之一时破坏对象的一部分是否是未定义的行为。


推荐阅读