在 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
我正在尝试编写一些代码,通过将函数调用及其参数存储在 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