从具有按值捕获的 lambda 移动构造 std::function 时,移动构造函数被调用两次

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

当从

lambda
移动构造 std::function 对象时(其中 lambda 具有按值捕获),值捕获 的对象的移动构造函数似乎被调用了两次。考虑

#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

我用

g++ -std=c++17
编译。

我猜这种行为可能在某种程度上依赖于编译器实现,但我仍然对该机制感到好奇。

有人可以解释一下为什么移动构造函数被调用两次以及那里到底发生了什么吗?

c++ lambda c++17 move-semantics std-function
2个回答
15
投票

这是由

std::function
的实现方式造成的。考虑以下更简单的示例:

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));    
}

它打印出

MM
,因此,当将其实例存储到
Lambda
时,
std::function
的移动构造函数会被调用两次。

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

在您的情况下,该 lambda 的

Foo
成员变量(通过值捕获)被移动了两次,因为 整个 lambda 被移动了两次。 请注意,捕获本身不会调用任何移动构造函数,而是调用复制构造函数。


为什么

std::function
的构造函数将参数移动两次?请注意,这个构造函数通过值传递其参数,然后,它在内部需要存储该对象。它可以通过以下函数进行模拟:

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


6
投票

我认为,这是因为

std::function
移动构造了它的参数
T
(即此处的 lambda)。

可以看到,只需将

std::function
替换为它的简单版本即可。

#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    
---------

如果不移动构造,它将复制参数,进而复制捕获变量。请参阅此处:https://godbolt.org/z/yyDQg_

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