Может ли указатель [this], захваченный лямбдой, быть недействительным до запуска лямбда? - PullRequest
6 голосов
/ 06 октября 2019

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

#include <vector>
#include <functional>
class Class_A
{
public:
    std::function<void()> getFunctionToRunLater()
    {
        return [this] () { somethingToDo(); moreThingsToDo(); };
        // Returns a lambda function that captures the this pointer,
        // so it can access the object's methods and variables.
    }

    void somethingToDo();
    void moreThingsToDo();
}

int main()
{
    std::vector<Class_A> vec;
    vec.push_back(Class_A());

    std::function<void()> pendingFunction = vec.back().getFunctionToRunLater();

   // More code...

   pendingFunction();
}

Все хорошо, верно? Мы получаем функцию, которую объект хочет запустить, и после некоторой логики мы выполняем эту функцию. Это представляет отправку функций в очередь, и они выполняют все функции в очереди. Но теперь посмотрим на это:

int main()
{
    std::vector<Class_A> vec;
    vec.push_back(Class_A());

    std::function<void()> pendingFunction = vec.back().getFunctionToRunLater();

   // More code...

   vec.reserve(1000);
   // This will surely reallocate the vector, invalidating all pointers.

   pendingFunction();
   // And now my program is going straight down to hell, right?
}

Правильно ли мое предположение? Что произойдет, если лямбда вообще ничего не захватывает, будет ли программа по-прежнему логически повреждена? А что, если лямбда не захватывает указатель this, а, скорее, какое-то другое поле класса?

Ответы [ 3 ]

3 голосов
/ 06 октября 2019

Если копирование объекта возможно, то вы можете захватить *this по значению: (требуется C ++ 17)

return [*this] { somethingToDo(); moreThingsToDo(); }

Это копирует весь объект в замыкание, чтобы избежать выхода изпожизненный доступ к исходному объекту.

3 голосов
/ 06 октября 2019

существующий ответ уже упоминает, что указатель может быть признан недействительным. Один из способов избежать этой проблемы, как уже упоминалось, состоит в изменении владельца *this либо на shared_ptr, unique_ptr, либо на копию. Однако это происходит за дополнительную плату (динамическое выделение или дополнительное копирование), а иногда просто невозможно (типы, не подлежащие копированию).

Вместо этого я бы предложил проект, который не приводит к этой проблеме впервое место, то есть не делать указатель this частью состояния лямбды. Возьмите объект в качестве параметра:

std::function<void(Class_A&)> getFunctionToRunLater()
{
    return [] (Class_A& obj) { obj.somethingToDo(); obj.moreThingsToDo(); };
}
3 голосов
/ 06 октября 2019

Да, эта программа может иметь проблемы. C ++ не защищает вас от аннулирования указателей, и, поскольку вы выделили объекты в вашем векторе, он может изменить адрес при изменении размера вектора, что вызовет проблемы, если вы попытаетесь запустить свою лямбду.

Вы, вероятно,быть не в состоянии скомпилировать программу без записи this. Вы также столкнетесь с проблемами, если попытаетесь захватить ссылки или указатели на любую часть вашего объекта, не будучи уверенными, что память, на которую указывает указатель, не будет перемещаться.

Как программа, подобная этой, стоит быть осторожным. не гарантируется сбой, даже если у вас есть ошибка, так как старые данные могут все еще существовать в памяти, даже когда ваш вектор изменяется. Поэтому, если вы пытаетесь захватить this и не видите проблем во время выполнения, это не означает, что ваша программа верна.

Для прямого решения я бы посмотрел на распределение ваших объектов в куче. используя один из типов интеллектуальных указателей, таких как std::unique_ptr или std::shared_ptr.

...