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

Pat*_*ght 2 c++ lambda std-function

我正在尝试编写一些代码,通过将函数调用及其参数存储在 lambda/std::function 中,允许我稍后调用函数。理想情况下,参数只会被复制一次(并以其他方式移动),但我可以实现的最小副本数似乎是 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;
}
Run Code Online (Sandbox Code Playgroud)

上述代码的输出是:

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
Run Code Online (Sandbox Code Playgroud)

这对我来说毫无意义。难道 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));
}
Run Code Online (Sandbox Code Playgroud)

输出:

main()
Test: Constructor
foo()
Test: Copy Constructor
Test: Copy Constructor
queue()
Test: Destructor
dequeue()
_foo()
t={a=20}
Test: Destructor
main() return
Test: Destructor
Run Code Online (Sandbox Code Playgroud)

但我仍然不明白额外的副本是从哪里来的。

有人可以帮助启发我吗?

dan*_*dam 6

AFAICT 问题在于const论证foo()t当您在内部捕获时foo(const Test& 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
Run Code Online (Sandbox Code Playgroud)

/sf/answers/2203960531/中提到的替代解决方案是在表单中使用 capture [t=t]

通过 move-capture 和其他两个更改,也可以消除剩余的复制构造函数:

- void foo(const Test& t) {
+ void foo(Test t) {
...
-    queue([t](){
+    queue([t =  std::move(t)](){
...
-    foo(test1);
+    foo(std::move(test1));
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)