I am trying to create a wrapper class for std::thread. This class provides a kick method which starts the thread and calls a pure virtual function. I am using a derived class to call this kick method and derived class also has implemented the virtual function.
class Executor
{
public:
// constructor
Executor();
// destructor
~Executor();
// kick thread execution
void Kick();
private:
// thread execution function
virtual void StartExecution() = 0;
// thread handle
std::thread mThreadHandle;
};
Following is the implementation of executor class
Executor::Executor()
{
// Nothing to be done here
}
Executor::~Executor()
{
if (mThreadHandle.joinable())
mThreadHandle.join();
}
void Executor::Kick()
{
// mThreadHandle = std::thread(&Executor::StartExecution, this);
mThreadHandle = std::thread([this] {this->StartExecution();});
}
I am using a Consumer class which inherits this class and implements StartExecution method. When i use the kick method it shows pure virtual function called and program terminates.
std::unique_ptr<Consumer> consumer = std::make_unique<Consumer>();
consumer->Kick();
In the executor kick method. I added a breakpoint and started looking what is wrong. It comes to the
mThreadHandle = std::thread([this] {this->StartExecution();});
line twice. First because of the kick method, second to execute the lambda function. The first time I see that this points to Consumer class. But when it comes to the lambda function it is messed up and the vptr is pointing to the pure virtual function.
I would be interested in what is wrong in this instead of simple answers.
Just a guess based on what I've tried: your Consumer
gets destructed before the thread executes.
I've made ~Executor
virtual and added some print statements for relevant function calls.
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
class Executor
{
public:
// constructor
Executor();
// destructor
virtual ~Executor();
// kick thread execution
void Kick();
private:
// thread execution function
virtual void StartExecution() { std::cout << "Executor::Kick\n"; }
// thread handle
std::thread mThreadHandle;
};
Executor::Executor()
{
// Nothing to be done here
}
Executor::~Executor()
{
std::cout << "~Executor\n";
if (mThreadHandle.joinable())
mThreadHandle.join();
}
void Executor::Kick()
{
// mThreadHandle = std::thread(&Executor::StartExecution, this);
mThreadHandle = std::thread([this] {this->StartExecution();});
}
class Consumer: public Executor {
public:
~Consumer() {
std::cout << "~Consumer\n";
}
private:
virtual void StartExecution() { std::cout << "Consumer::Kick\n"; }
};
int main() {
{
std::cout << "1:\n";
std::unique_ptr<Consumer> consumer = std::make_unique<Consumer>();
consumer->Kick();
}
{
std::cout << "2:\n";
std::unique_ptr<Consumer> consumer = std::make_unique<Consumer>();
consumer->Kick();
std::cout << "Sleeping for a bit\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
Output:
1:
~Consumer
~Executor
Executor::Kick
2:
Sleeping for a bit
Consumer::Kick
~Consumer
~Executor
See it run here
Sleeping before destroying the consumer lets the thread run and call the correct function. A "real" solution would be to ensure that the consumer lives at least as long as the thread itself. Since the thread exists in the base class Executor
this isn't guaranteed, as derived classes are destructed before base classes.
From cppreference (emphasis mine):
When a virtual function is called directly or indirectly from a constructor or from a destructor (including during the construction or destruction of the class’s non-static data members, e.g. in a member initializer list), and the object to which the call applies is the object under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class. In other words, during construction or destruction, the more-derived classes do not exist.
This appears to apply when a member function is called in a different thread during construction/destruction.
Thr program goes out of memory even before the thread gets a chance to call the function. If you change your code like this
void Executor::Kick()
{
mThreadHandle = std::thread([this] {this->StartExecution();});
this_thread::sleep_for(chrono::seconds(1)); // any number
}
this will work.
That's the precise reason why you can't pass the this
by reference in capture list
Now about your specific question
I would be interested in what is wrong in this instead of simple answers.
The vPTR points to VTable and as the class goes out of the memory, the vPTR points to base class VTable and hence this is happening. you can check the same by printing the vTable address before calling the function
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With