在lambda运行之前,lambda捕获的[this]指针可以无效吗?

时间:2019-10-06 13:39:17

标签: c++ lambda this

假设我们有一个带有某些对象的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?
}

我的假设正确吗?如果lambda根本不捕获任何东西,该程序在逻辑上是否仍会损坏?如果lambda没有捕获this指针,而是捕获了其他一些类字段,该怎么办?

3 个答案:

答案 0 :(得分:3)

是的,此程序可能有问题。 C ++不能保护您避免使指针无效,并且正如您所强调的那样,当矢量调整大小时,矢量中的对象可能会移动地址,如果您尝试运行lambda,这将导致问题。

如果不捕获yes,您可能将无法编译程序。如果您尝试捕获对对象任何部分的引用或指针而又不确定所指向的内存不会移动,那么也会遇到问题。

请务必谨慎,因为即使有错误,也不保证这样的程序会崩溃,因为即使矢量调整大小,旧数据仍可能存在于内存中。因此,如果您尝试捕获this并在运行时看不到任何问题,那并不意味着您的程序是正确的。

对于简单的解决方案,我将考虑使用thisstd::unique_ptr等智能指针类型之一在堆上分配对象。

答案 1 :(得分:3)

如果可以复制对象,则可以按值捕获*this :(需要C ++ 17)

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

这会将整个对象复制到闭包中,以避免终生访问原始对象。

答案 2 :(得分:3)

BackgroundTask.swift已经提到指针可以无效。如上所述,一种避免问题的方法是通过*thisshared_ptr或副本来更改unique_ptr的所有权。但是,这需要额外的费用(动态分配或额外的副本),有时甚至是不可能的(不可复制的类型)。

相反,我建议设计一种首先不会导致此问题的设计,即,不要使this指针成为lambda状态的一部分。将该对象作为参数:

std::function<void(Class_A&)> getFunctionToRunLater()
{
    return [] (Class_A& obj) { obj.somethingToDo(); obj.moreThingsToDo(); };
}