c++ - 如何移动包含和捕获 lambda 的 std::function?
问题描述
我正在尝试提出以下最有效(即最少数量的副本)的实现。我有一个队列,用于在稍后的某个时间点执行 std::function<void()> 对象。队列只能采用微不足道的数据类型,因此我需要将指针传递给 std::function 而不是 std::function 本身(即队列包含 std::function<void()>* 类型)。
理想情况下,如果在 lambda 中(按值)捕获了一条数据,则在创建 std::function 并将其添加到队列的整个过程中,应该只制作该数据的一个副本。
这是我一直在玩的一个例子:
std::function<void()>* invoke(const std::function<void()>& fn) {
printf("invoke()\r\n");
return new std::function<void()>(fn);
}
我会这样使用
class Base {
public:
virtual void sayHello() const = 0;
};
class A : public Base {
public:
A() {
printf("A::A()\r\n");
}
A(const A& a) {
printf("A copy constructor\r\n");
}
A(const A&& a) {
printf("A const move constructor\r\n");
}
A(A&& a) {
printf("A move constructor\r\n");
}
A& operator=(A&& other)
{
printf("A move assignment operator\r\n");
return other;
}
void sayHello() const override { printf("A says hello\r\n"); }
};
int main() {
A myA;
printf("invoking lambda which calls myA.sayHello()\r\n");
std::function<void()> *fn = invoke([myA](){
myA.sayHello();
});
return 0;
}
由于 lambda 是按值捕获对象 (myA),因此在最初创建 lambda 时会制作一份副本。现在,由于您在 main() 中看到的 lambda 是“临时的”(仅在调用调用中使用),因此实际上应该只有一个 lambda 副本挂起,因此只有一个 myA 的(附加)副本。
但是,这是我的输出示例:
invoking lambda which calls myA.sayHello()
A copy constructor
A move constructor
invoke()
A copy constructor
看来,当我在堆上创建一个新的 std::function 时,它正在复制 lambda(以及 myA)而不是仅仅移动它,这是我想要的行为,因为原始 lambda 和 std::function (将 lambda 传递给调用函数时自动创建的那个)无论如何只是暂时的。
我觉得我很接近,但在这里误解了一些东西。有人可以帮忙吗?
谢谢!
编辑
基于这里的所有讨论,我对我的代码进行了一些修改。现在我将其升级为一个更复杂的示例,如下所示:
//Same A and Base classes as above
inline static void invoke(std::function<void()> fn) {
printf("invoke(std::function<void()> fn)\r\n");
std::function<void()>* newF = new std::function<void()>(std::move(fn));
(*newF)();
delete newF;
printf("return\r\n");
}
int main() {
printf("Starting Tests...\r\n");
A myA;
invoke([myA](){
myA.sayHello();
});
return 0;
}
它具有以下意外输出:
Starting Tests...
A::A()
A copy constructor
A move constructor
invoke(std::function<void()> fn)
A says hello
A::~A()
return
A::~A()
A::~A()
我不知道为什么要对析构函数进行第三次调用,因为应该只存在 A 对象的 2 个副本(main() 中的原始副本和 lambda 中的复制构造函数制作的副本)。
解决方案
您的示例不起作用,因为您试图std::move
从const
参考中获取。这将产生一个 type std::function<...> const&&
,但 move 的构造函数std::function
只接受std::function<...>&&
(没有const
)。这在 C++ 中并不罕见,因为const
右值引用是该语言的一个奇怪的极端情况,在概念上没有多大意义。特别是,您不能合理地const
从右值引用移动,因为不允许您对源进行任何更改。
如果目的是保证不会发生副本,则应通过右值引用接受参数:
std::function<void()>* invoke(std::function<void()>&& fn) {
printf("invoke()\r\n");
return new std::function<void()>(std::move(fn));
}
请注意,这将函数限制为仅使用右值参数。如果您更喜欢更灵活的设计,请考虑max66 的答案。
推荐阅读
- c# - 在 Azure BlobClient 上设置 ContentType 而不擦除 ContentHash 等属性
- excel - 在基于公共列比较两列之间的值时需要帮助
- python - Python Pandas - 使用 .loc 在多列上使用 AND 和 OR 进行选择
- git - IntelliJ IDEA Ultimate Edition - 更新项目后无法查看 Git 提交
- ios - 使用 PageViewController 运行 WKWebView 时,滑动不起作用
- reactjs - 如何从使用效果中访问状态变量数据?
- memory-management - c API之excelDNA内存管理
- python-3.x - Python:如果数组包含重复项,则将字符串编辑为(多少个重复项)项
- python - Collatz连词和答案格式
- python - “NoneType”和“datetime.datetime”不支持的操作数类型