考虑:
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可以移动任何不同的情况吗?
答案 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保持可复制。