C++ 如果lambda在运行时被移动/破坏,会发生什么情况?

C++ 如果lambda在运行时被移动/破坏,会发生什么情况?,c++,c++11,lambda,undefined-behavior,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](); std::向量向量向量机; 不可移动的东西; 向量向后推([&vec,m](){ 向量调整(100); //与'm'有关的事情 }); vec[0](); v

考虑:

std::vector<std::function<void()>> vec;
something_unmovable m;
vec.push_back([&vec, m]() {
    vec.resize(100);
    // things with 'm'
});
vec[0]();
std::向量向量向量机;
不可移动的东西;
向量向后推([&vec,m](){
向量调整(100);
//与'm'有关的事情
});
vec[0]();
vec.resize(100)
可能会导致重新分配向量,这意味着
std::function
s将被复制到新位置,而旧位置将被销毁。然而,这种情况发生时,旧的一个仍然在运行。这段特定的代码之所以运行,是因为lambda不做任何事情,但我认为这很容易导致未定义的行为

那么,到底发生了什么?
m
是否仍然可以从向量访问?或者是lambda的
这个
指针现在无效(指向释放的内存),因此lambda捕获的任何内容都不可访问,但是如果它运行的代码没有使用它捕获的任何内容,它就不是未定义的行为


另外,lambda可移动的情况有什么不同吗?

您可以像对待普通结构实例一样对待lambda捕获

就你而言:

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

    void operator()()
    {
        this->vec.resize(100);
    }
};
struct lambda\u UUID\u HERE\u stuff
{
std::vector&vec;
不可移动的东西;
void运算符()()
{
此->矢量大小调整(100);
}
};
…我相信所有规则都适用(就VS2013而言)


因此,这似乎是另一种未定义行为的情况。也就是说,如果
&vec
恰好指向包含捕获实例的向量,并且
操作符()
中的操作会导致该向量调整大小。

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

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

这些规则允许在执行成员函数时销毁对象,只要成员函数在执行后不使用
this
。您的示例是一个不寻常的示例,更常见的示例是非静态成员函数执行
delete this,解释这是允许的

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

因此,要回答您的问题:

或者是lambda的this指针现在无效(指向已释放的内存),因此lambda捕获的任何内容都不可访问,但如果它运行的代码不使用它捕获的任何内容,这不是未定义的行为

是的,差不多

此外,lambda可移动的情况是否有任何不同

不,不是


lambda被移动的唯一时间可能是在lambda被移动之后。在您的示例中,
操作符()
继续在从原始函子移动并随后销毁的函子上执行。

最终,这个问题中有许多细节与此无关。我们可以将其简化为询问以下各项的有效性:

struct A {
    something_unmovable m;

    void operator()() {
        delete this;
        // do something with m
    }
};
问问你的行为。毕竟,
resize()
的作用是调用对象函数调用的析构函数。无论它是从std::vector中移动还是从std::vector中复制,都无关紧要——无论哪种方式,它都会随后被销毁

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

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

因此,如果
something_unmovable
的析构函数是非平凡的(这将使
A
的析构函数或您的lambda非平凡的),则调用析构函数后对
m
的任何引用都是未定义的行为。如果不可移动的东西有一个微不足道的析构函数,那么你的代码是完全可以接受的。如果在
删除此
(您问题中的
resize()
)之后您没有做任何事情,那么这是完全有效的行为

m仍然可以从向量访问吗


是的,
vec[0]
中的函子仍将包含
m
。它可能是原始lambda,也可能是原始lambda的副本。但无论如何都会有一个
m

“不可移动”是指“不可复制”,还是
m
可复制?(通俗地说,如果某个东西是可复制的,那么它会自动移动,因为副本是移动的有效实现。)您不能创建捕获不可复制值()的lambda。我把“unmobile”理解为只有已删除的移动构造函数,而其他构造函数是默认值。在这些假设下,我们有可能做出一个决定。我认为问题的重点是解决当捕获本身被销毁时,lambda捕获的内容会发生什么变化。好了,它们被给予了与结构相同的处理。这是一个相当空洞的问题,除非你证明它是结构的UB。@LightnessRacesinOrbit:我应该说“如果
&vec
恰好指向包含结构的向量,那么它就是UB。”更新…它有点被,其中解释了即使对象被破坏,非静态成员函数也可以在没有UB的情况下继续运行,只要成员函数在对象被删除后不访问该对象。@hvd:我不确定我是否相信。有引证会更好。@Light