在 std::function 中捕获 lambda 会产生额外的副本

Capturing lambda in std::function results in extra copies

我正在尝试编写一些代码,通过将函数调用及其参数存储在 lambda/std:: 函数中,允许我稍后调用函数。理想情况下,参数只会被复制一次(并以其他方式移动)但我能实现的最小副本数似乎是 2.

//==============================================================================
// INCLUDES
//==============================================================================

#include <iostream>
#include <functional>
#include <memory>

//==============================================================================
// VARIABLES
//==============================================================================

static std::unique_ptr<std::function<void()>> queueFunction;

//==============================================================================
// CLASSES
//==============================================================================

class Test {
public:
    Test(int a, int b = 20, int c = 30) : _a(a), _b(b), _c(c) {
        std::cout << "Test: Constructor" << std::endl;
    }
    
    ~Test() {
        std::cout << "Test: Destructor" << std::endl;
    }
    
    Test(const Test& other) :
        _a(other._a)
    {
        std::cout << "Test: Copy Constructor" << std::endl;
    }
    
    Test(Test&& other) :
        _a(std::move(other._a))
    {
        std::cout << "Test: Move Constructor" << std::endl;
    }
    
    Test& operator=(const Test& other) {
        if (this != &other) {
            _a = other._a;
        
            std::cout << "Test: Assignment Operator" << std::endl;
        }
        
        return *this;
    }
    
     Test& operator=(Test&& other) {
        if (this != &other) {
            _a = std::move(other._a);
        
            std::cout << "Test: Move Assignment Operator" << std::endl;
        }
        
        return *this;
    }
    
    friend std::ostream& operator<<(std::ostream& os, const Test& v) {
        os << "{a=" << v._a << "}";
        return os;
    }
    
private:
    int _a;
    int _b;
    int _c;
};

//==============================================================================
// FUNCTIONS
//==============================================================================

void foo(const Test& t);
void _foo(const Test& t);

template <typename F>
void queue(F&& fn) {
    std::cout << "queue()" << std::endl;
    
    queueFunction = std::make_unique<std::function<void()>>(std::forward<F>(fn));
}

void dequeue() {
    std::cout << "dequeue()" << std::endl;
    
    if (queueFunction) {
        (*queueFunction)();
    }
    
    queueFunction.reset();
}

void foo(const Test& t) {
    std::cout << "foo()" << std::endl;
    
    queue([t](){
       _foo(t); 
    });
    
    //Only a single copy of Test is made here
    /*
    [t](){
       _foo(t); 
    }();
    */
}

void _foo(const Test& t) {
    std::cout << "_foo()" << std::endl;
    std::cout << "t=" << t << std::endl;
}


//==============================================================================
// MAIN
//==============================================================================

int main() {
    std::cout << "main()" << std::endl;
    
    Test test1(20);
    
    foo(test1);
    dequeue();
    
    std::cout << "main() return" << std::endl;
    
    return 0;
}

以上代码的输出为:

main()
Test: Constructor
foo()
Test: Copy Constructor
queue()
Test: Copy Constructor
Test: Copy Constructor
Test: Destructor
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor

这对我来说毫无意义。 lambda 不应该捕获一次 Test 的实例,然后将该 lambda 一直转发到新的 std::function 从而导致移动吗?

如果我这样修改我的队列函数,我至少可以摆脱一次复制。

void queue(std::function<void()> fn) {
    std::cout << "queue()" << std::endl;
    
    queueFunction = std::make_unique<std::function<void()>>(std::move(fn));
}

输出:

main()
Test: Constructor
foo()
Test: Copy Constructor
Test: Copy Constructor
queue()
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor

但我还是不明白多余的副本是从哪里来的。

谁能帮忙解惑一下?

AFAICT 问题是 foo() 参数的 const。当您在 foo(const Test& t) 中捕获 t 时,lambda 中捕获的类型也是 const。稍后当您转发 lambda 时,lambda 的移动构造函数将别无选择,只能复制,而不是移动,捕获。您不能从 const 移动。 将 foo 更改为 foo(Test& t) 后,我得到:

main()
Test: Constructor
foo()
Test: Copy Constructor
queue()
Test: Move Constructor
Test: Move Constructor
Test: Destructor
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor

中提到的替代解决方案是使用 [t=t].

形式的捕获

通过移动捕获和其他两个更改,也可以消除剩余的复制构造函数:

- void foo(const Test& t) {
+ void foo(Test t) {
...
-    queue([t](){
+    queue([t =  std::move(t)](){
...
-    foo(test1);
+    foo(std::move(test1));
main()
Test: Constructor
Test: Move Constructor
foo()
Test: Move Constructor
queue()
Test: Move Constructor
Test: Move Constructor
Test: Destructor
Test: Destructor
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor