当使用 std::move 和 lambda 时,何时会发生移动

When using std::move with a lambda when does the move happen

本文关键字:何时会 移动 std move lambda      更新时间:2023-10-16

如果我在函数中创建 lambda 并使用 std::move 捕获一个变量到 lambda,什么时候会发生移动?是在创建 lambda 时还是在执行 lambda 时?

以以下代码为例...各种动作何时发生?如果在一个线程上调用 myFunction,而在另一个线程上执行 testLambda,线程是否安全?

class MyClass {
private:
// Only accessed on thread B
std::vector<int> myStuff;
// Called from thread A with new data
void myFunction(const std::vector<int>&& theirStuff) {
// Stored to be called on thread B 
auto testLambda = [this, _theirStuff{ std::move(theirStuff) }]() { 
myStuff = std::move(_theirStuff);
};
// ... store lambda
}
// Elsewhere on thread A
void someOtherFunction() {
std::vector<int> newStuff = { 1, 2, .... n };
gGlobalMyClass->myFunction(std::move(newStuff));
}

如果我在函数中创建一个 lambda 并使用 std::move 捕获一个变量到lambda,什么时候会发生移动?是在创建 lambda 时还是在执行 lambda 时?

如果你写了我相信你打算写的东西,那么答案将是:两者兼而有之。目前,答案是:两者都不是。您有一个 lambda 捕获_theirStuff { std::move(theirStuff) }.这基本上声明了闭包类型的一个成员,该成员将在创建闭包对象时初始化,就好像它是

auto _theirStuff { std::move(theirStuff) };

你也有

myStuff = std::move(_theirStuff);

在 lambda 正文中。

但是,您的参数theirStuff实际上是对const std::vector<int>的右值引用。因此,_theirStuff { std::move(theirStuff) }实际上不会执行移动,因为无法从const std::vector移动。最有可能的是,你想写std::vector<int>&& theirStuff。此外,正如@JVApen在下面的评论中指出的那样,您的 lambda 是不可变的。因此,_theirStuff实际上也是常量,因此也无法移动。因此,尽管有所有std::move,您上面的代码实际上每次都会复制向量。如果你写过

void myFunction(std::vector<int>&& theirStuff)
{
auto testLambda = [this, _theirStuff { std::move(theirStuff) }]() { 
myStuff = std::move(_theirStuff);
};
}

创建闭包对象时,您将theirStuff移动到_theirStuff中。当调用 lambda 时,您将_theirStuff复制到myStuff中。如果你写过

void myFunction(std::vector<int>&& theirStuff)
{
auto testLambda = [this, _theirStuff { std::move(theirStuff) }]() mutable { 
myStuff = std::move(_theirStuff);
};
}

然后,您将在创建闭包对象时将theirStuff移动到_theirStuff中。当调用 lambda 时,您将_theirStuff移动到myStuff中。请注意,因此,您的 lambda 实际上不能被调用两次。我的意思是,它可以,但它只会真正工作一次,因为_theirStuff第一次调用 lambda 后将是空的......

另请注意,上述描述仅对示例中的特定类型组合有效。对于移动对象的实际含义,没有通用的定义。移动对象的含义完全取决于对象的特定类型。它甚至可能没有任何意义。std::move本身并没有真正做任何事情。它所做的只是将给定的表达式强制转换为右值引用。如果随后从std::move的结果初始化另一个对象,或将结果分配给对象,重载解析将选取移动构造函数或移动赋值运算符(如果存在),而不是普通的复制构造函数或复制赋值运算符。然后由相应类型的移动构造函数/移动赋值运算符的实现来实际执行移动,即,在初始化或从右值赋值的情况下,为特定类型执行任何应该做的事情。因此,在某种程度上,当您应用std::move时,您要做的是将相应的对象宣传为"这可能会被移动"。它是否真的会被移出(如果是这样,这实际上意味着什么)取决于实现。在std::vector的特殊情况下,根据定义,移动构造函数/移动赋值运算符不仅保证原始向量的内容将从原始对象接管,而且原始对象之后将为空。在许多其他情况下,对从中移动的对象执行任何操作都可能是未定义的行为(除了,也许,销毁它;该对象几乎可以被视为一种类型,至少不允许这样做几乎是无用的;通常,您至少能够为从中移动的对象分配一个新值, 但即使这样也不能保证)。您始终必须检查手头的特定类型,对象在从

...