如果lambda在运行时被移动/破坏会发生什么?

时间:2015-07-18 17:38:12

标签: c++ c++11 lambda undefined-behavior

考虑:

std::vector<std::function<void()>> vec;
something_unmovable m;
vec.push_back([&vec, m]() {
    vec.resize(100);
    // things with 'm'
});
vec[0]();

vec.resize(100)可能会导致向量的重新分配,这意味着std::function将被复制到新位置,而旧的位置将被销毁。然而,当旧的仍在运行时,就会发生这种情况。这个特定的代码运行是因为lambda没有做任何事情,但我想这很容易导致未定义的行为。

那么,究竟发生了什么? m是否仍可从向量中访问?或者是lambda的this指针现在是无效的(指向释放的内存),因此lambda捕获的任何内容都无法访问,但如果它运行的代码不使用它捕获的任何内容,则它不是未定义的行为?

此外,lambda可以移动任何不同的情况吗?

4 个答案:

答案 0 :(得分:6)

正如其他答案已经涵盖的那样,lambdas本质上是语法糖,可以轻松创建提供自定义operator()实现的类型。这就是为什么你甚至可以使用operator()的显式引用来编写lambda调用,如:int main() { return [](){ return 0; }.operator()(); }。所有非静态成员函数的相同规则也适用于lambda实体。

这些规则允许在执行成员函数时销毁对象,只要成员函数之后不使用this即可。您的示例是不常见的,更常见的示例是执行delete this;的非静态成员函数。 This made it into the C++ FAQ,解释说它是允许的。

据我所知,标准允许这不是真正解决它。它以一种不依赖于未被破坏的对象的方式描述成员函数的语义,因此实现必须确保即使对象被破坏也要让成员函数继续执行。

所以回答你的问题:

  

或者是lambda的this指针现在无效(指向释放的内存),所以lambda捕获的任何内容都无法访问,但如果它运行的代码不能使用它捕获的任何内容,那么&# 39;不是未定义的行为?

是的,差不多。

  

此外,lambda可以移动任何不同的情况吗?

不,不是。

可移动lambda的唯一时间是在lambda移动之后。在您的示例中,operator()继续执行原始移动,然后销毁的仿函数。

答案 1 :(得分:4)

您可以将lambda捕获视为普通的struct实例。

在你的情况下:

struct lambda_UUID_HERE_stuff
{
    std::vector<std::function<void()>> &vec;
    something_unmovable m;

    void operator()()
    {
        this->vec.resize(100);
    }
};

......我相信所有相同的规则都适用(就VS2013而言)。

所以,这似乎是另一种未定义行为的情况。也就是说,如果&vec碰巧指向包含捕获实例的向量,则operator()内的操作会导致该向量调整大小。

答案 2 :(得分:3)

最终,这个问题中有很多细节并不相关。我们可以减少它来询问有效性:

struct A {
    something_unmovable m;

    void operator()() {
        delete this;
        // do something with m
    }
};

并询问这种行为。毕竟,resize()的影响是调用对象的中间函数析构函数。它是由std::vector移动还是复制 - 并不重要 - 无论哪种方式随后都会被销毁。

标准告诉我们[class.cdtor]:

  

对于具有非平凡的对象   析构函数,在析构函数完成后引用对象的任何非静态成员或基类   执行导致未定义的行为。

因此,如果something_unmovable的析构函数是非平凡的(它会使A的析构函数 - 或者你的lambda - 非平凡的),那么对m之后的任何引用都是something_unmovable析构函数被调用是未定义的行为。如果delete this 有一个简单的析构函数,那么您的代码是完全可以接受的。如果您resize()(问题中为vec[0])后执行任何操作,那么这是非常有效的行为。

  

是否仍然可以从矢量访问?

是的,m中的仿函数仍会包含m。它可能是原始的lambda - 或者它可能是原始lambda的副本。但是会有$_SESSION这种方式。

答案 3 :(得分:-1)

函数对象通常是可复制的,因此lambda将继续运行而不会产生不良影响。如果它通过引用AFAIR捕获,内部实现将使用std :: reference_wrapper,以便lambda保持可复制。