当移动发生时,使用带有lambda的std :: move

问题描述 投票:2回答:1

如果我在函数中创建一个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));
}
c++ std
1个回答
1
投票

如果我在函数中创建一个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实际上也将是const,因此也无法移动。因此,尽管所有的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的特定情况下,移动构造函数/移动赋值运算符,根据定义,保证不仅原始向量的内容将从原始对象接管,而且原始对象之后将为空。在许多其他情况下,对于从被移动的对象做任何事情可能是未定义的行为(除了,可能,销毁它;那个可以被认为是理所当然的类型,至少不允许这样做几乎没用;通常,您至少能够为移动的对象分配新值,但即使这样也不能保证。您总是必须检查手头的特定类型,在移动之后保证对象的状态是什么...

© www.soinside.com 2019 - 2024. All rights reserved.