I am finding it very strange. Please, help me to explain this. I have a class which starts infinite loop in a separate thread, and two classes which inherit it. One of the classes implements the interface to be triggered outside as std::shared_ptr
, and another one class hold this interface as std::weak_ptr
. Please look at the code below. Sorry for a lot of code, I was trying to be as short as it possible to reproduce the error. Why sometimes have I pure virtual call in Sender::notify
function? As far as I know std::shared_ptr
is reentrant.
#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;
}
Polymorphism doesn't work as you may expect during construction and destruction. The current type is the most derived type that still exists. When you are in Thread::~Thread
the Sender
part of your object has already been completely destroyed so it wouldn't be safe to call its overrides.
When thread_fun
tries to run loop_iterator()
before the constructor finishes or after the destructor starts, it will not polymorphically dispatch, but instead it will call Thread::loop_iteration
which is a pure virtual function (= 0
).
See https://en.cppreference.com/w/cpp/language/virtual#During_construction_and_destruction
Here is a demonstration of this : https://godbolt.org/z/4vsPGYq97
The derived
object is destroyed after one second, at which point you see the output change indicating that the virtual function being called changes at that point.
I'm not sure if this code is valid, or if destroying the derived
part of the object while one of its member function is being executed is Undefined Behavior.