С ++ лямбда-функция вызывает чисто виртуальную функцию - PullRequest
0 голосов
/ 02 июня 2018

Я пытаюсь создать класс-оболочку для std :: thread.Этот класс предоставляет метод kick, который запускает поток и вызывает чисто виртуальную функцию.Я использую производный класс для вызова этого метода, и производный класс также реализовал виртуальную функцию.

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

Ниже приведена реализация класса исполнителя

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 , который наследует этот класс и реализует метод StartExecution.Когда я использую метод kick, он показывает чисто вызванную виртуальную функцию и программа завершается.

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

В методе executor kick.Я добавил точку останова и начал искать, что не так.Он приходит к строке

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

строка дважды.Первый из-за метода kick, второй - для выполнения лямбда-функции.В первый раз я вижу, что этот указывает на класс потребителей .Но когда дело доходит до лямбда-функции, она испорчена, и vptr указывает на чисто виртуальную функцию.

Я бы интересовался, что в этом не так, а непростые ответы .

Ответы [ 2 ]

0 голосов
/ 02 июня 2018

Программа теряет память даже до того, как поток получит возможность вызвать функцию.Если вы измените свой код следующим образом

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

, это будет работать.

Именно по этой причине вы не можете передать this по ссылке в списке захвата

Теперь о вашем конкретном вопросе

Я быинтересоваться тем, что не так в этом, вместо простых ответов.

vPTR указывает на VTable, и, когда классу не хватает памяти, vPTR указывает на базовый класс VTable и, следовательно, этоэто происходит.то же самое можно проверить, напечатав адрес vTable перед вызовом функции

0 голосов
/ 02 июня 2018

Просто предположение, основанное на том, что я пробовал: ваш 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

Смотрите его здесь

Сон перед уничтожением потребителя позволяет потоку работать и вызывать правильную функцию.«Реальным» решением было бы обеспечение того, чтобы потребитель жил как минимум столько же, сколько сам поток.Поскольку поток существует в базовом классе Executor, это не гарантируется, поскольку производные классы уничтожаются перед базовыми классами.

С cppreference (выделение мое):

Когда виртуальная функция вызывается прямо или косвенно из конструктора или из деструктора (в том числе во время создания или уничтожения нестатических элементов данных класса, например, в списке инициализатора члена), и объекта, к которому относитсяcall apply - это объект, находящийся в процессе строительства или уничтожения, вызываемая функция - это последний переопределитель в классе конструктора или деструктора, а не тот, который переопределяет его в более производном классе. Другими словами, во время конструирования или уничтожения более производные классы не существуют.

Это, кажется, применяется, когда функция-член вызывается в другом потоке во время конструирования /уничтожение.

...