提问者:小点点

从具有by-value捕获的lambda构造std::function时调用了两次Move构造函数


当从lambda中移动构造对象(该lambda具有by-value捕获)时,被捕获值的对象的移动构造函数似乎被调用了两次。考虑

#include <functional>
#include <iostream>

struct Foo
{
    int value = 1;

    Foo() = default;

    Foo(const Foo &) {}

    Foo(Foo &&)
    {
        std::cout << "move ctor" << std::endl;
    }
};

int main()
{
    Foo foo;
    auto lambda = [=]() { return foo.value; };
    std::cout << "---------" <<  std::endl;
    std::function<int()> func(std::move(lambda));
    std::cout << "---------" <<  std::endl;
    return 0;
}

输出为

---------
move ctor
move ctor
---------

我在Mac OS X Catalina上工作,我的编译器是

g++-9 (Homebrew GCC 9.3.0) 9.3.0

我使用进行编译。

我猜这种行为可能有点依赖于编译器实现,但我仍然对其机制感到好奇。

有人能不能请解释一下为什么移动构造函数被调用了两次,那里到底发生了什么?


共2个答案

匿名用户

这是由的实现方式造成的。考虑下面这个简单得多的例子:

struct Lambda
{
  Lambda() = default;
  Lambda(const Lambda&) { std::cout << "C"; }
  Lambda(Lambda&&) { std::cout << "M"; }
  void operator()() const { }
};

int main()
{
  auto lambda = Lambda();
  std::function<void()> func(std::move(lambda));    
}

它打印出,因此,在将其实例存储到中时,调用了两次的move构造函数。

现场演示:https://godbolt.org/z/xihndc

在您的示例中,该lambda的成员变量(由值捕获)被移动了两次,因为整个lambda被移动了两次。请注意,捕获本身并不调用任何移动构造函数,而是调用复制构造函数。

为什么的构造函数将参数移动两次?注意,这个构造函数通过值传递参数,然后,它需要在内部存储那个对象。可以使用以下功能进行kind-of模拟:

template< class F >
void function( F f )
{
    F* ptr = new F(std::move(f));
    delete ptr;
}

此代码:

  auto lambda = Lambda();
  function(std::move(lambda));

然后执行两个动作。

现场演示:https://godbolt.org/z/qzvvwa

匿名用户

我认为,这是因为移动构造了它的参数

只要用的简单版本替换它,就可以看到这一点。

#include <iostream>

struct Foo
{
   int value = 1;
   Foo() = default;
   Foo(const Foo&) { std::cout << "Foo: copy ctor" << std::endl; }
   Foo(Foo&&)
   {
      std::cout << "Foo: move ctor" << std::endl;
   }
};


template<typename T>
class MyFunction
{
   T mCallable;

public:
   explicit MyFunction(T func)
      //  if mCallable{ func}, it is copy constructor which has been called
      : mCallable{ std::move(func) }  
   {}
};

int main()
{
   Foo foo;
   auto lambda = [=]() { return foo.value; };
   std::cout << "---------" << std::endl;
   MyFunction<decltype(lambda)> func(std::move(lambda));
   std::cout << "---------" << std::endl;
   return 0;
}

产出:

Foo: copy ctor    
---------    
Foo: move ctor    
Foo: move ctor    
---------

如果不是move构造的,它将复制参数,而参数又复制captures变量。请参阅:https://godbolt.org/z/yydqg_