C++ lambda函数调用纯虚函数

c++ lambda function calls pure virtual function

我正在尝试为 std::thread 创建包装器 class。 class 提供了启动线程并调用纯虚函数的 kick 方法。我正在使用一个派生的 class 来调用这个 kick 方法并且派生的 class 也已经实现了虚函数。

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;
};

下面是执行器的实现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();});
}

我正在使用 Consumer class 继承此 class 并实现 StartExecution 方法。当我使用 kick 方法时,它显示调用了纯虚函数并且程序终止。

std::unique_ptr<Consumer> consumer = std::make_unique<Consumer>();
consumer->Kick();

在executor kick方法中。我添加了一个断点并开始寻找问题所在。说到

mThreadHandle = std::thread([this] {this->StartExecution();});

行两次。首先是因为 kick 方法,其次是执行 lambda 函数。第一次看到this指向Consumerclass。但是当涉及到 lambda 函数时,它就搞砸了,vptr 指向纯虚函数。

我会对其中​​的错误感兴趣,而不是简单的回答

只是根据我的尝试做出的猜测:您的 Consumer 在线程执行之前被破坏。

我已将 ~Executor 虚拟化并为相关函数调用添加了一些打印语句。

#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;
}

输出:

1:
~Consumer
~Executor
Executor::Kick
2:
Sleeping for a bit
Consumer::Kick
~Consumer
~Executor

See it run here

在销毁消费者之前休眠让线程 运行 并调用正确的函数。 "real" 解决方案是确保消费者至少与线程本身一样长。由于线程存在于基础 class Executor 中,因此无法保证这一点,因为派生的 classes 在基础 classes.

之前被破坏

来自 cppreference(强调我的):

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.

这似乎适用于 construction/destruction 期间在不同线程中调用成员函数的情况。

Thr 程序甚至在线程有机会调用该函数之前就已耗尽内存。如果您像这样更改代码

void Executor::Kick()
{
    mThreadHandle = std::thread([this] {this->StartExecution();});
    this_thread::sleep_for(chrono::seconds(1)); // any number
}

这会奏效。

这就是您无法在捕获列表中通过引用传递 this 的确切原因

现在关于你的具体问题

I would be interested in what is wrong in this instead of simple answers.

vPTR 指向 VTable,当 class 离开内存时,vPTR 指向基础 class VTable,因此发生了这种情况。您可以在调用函数之前通过打印 vTable 地址来检查是否相同