容器如何处理 lambda 表达式的参数

How container handles the arguments of a lambda expression

本文关键字:lambda 表达式 参数 处理 何处理      更新时间:2023-10-16

继几个月前我之前的问题之后,我想问一个容器如何在内部处理lampda表达式。

假设以下代码段:

#include <iostream>
#include <type_traits>
#include <queue>
#include <functional>
std::queue<std::function<void()>> funcs;
class A
{
public:
A()
{
std::cout << "A constructor" << std::endl;
}
void foo(int a)
{
}
};
class ThePool
{
public:
template <typename _Callable, typename Object, typename... _Args>
void QueueFunction(_Callable __f, Object& obj, _Args... __args)
{
funcs.push([__f, &obj, &__args...]() mutable
{
(obj.*__f)(__args...);
});
}
};
int main(int argc, char** argv)
{
ThePool t;
A a;
int x = 5;
t.QueueFunction(&A::foo, a, x);
std::function<void()> func = funcs.back();
func();
return 0;
}

如果将类 A 和 args 的实例作为值传递,则很明显这些值被复制。我混淆了如果我将类 A 的实例作为引用和 args 作为引用传递会发生什么。我知道在容器内部复制发生。从我的角度来看,函数指针的地址将被复制。是否也要复制 obj 和参数的实例?

举个例子,我注意到当我传递 A 实例作为引用并最终提取队列的函数 f 并将其称为 A 实例时,这是不一样的。

容器确实可以自由复制其元素。此外,std::function要求存储的函子是CopyConstructible的,它肯定会使用它。因此,您必须选择:

  • 如果按值传递对象,则实际上不必担心对象生存期。但是,在对象的副本(或使用复制的参数(上调用成员函数可能不是您想要的。

  • 如果通过(某种形式的(引用传递对象(例如,将它们包装在std::ref中(,您将永远不会创建对象和参数的副本,但在调用排队成员函数之前,它们可能会超出范围。

目前,在您的原始代码中,您正在 lambda 中创建obj的副本(obj是一个Obj&但这对 lambda 捕获无关紧要 - 如果没有通过引用显式捕获,它会复制(。要在 lambda 中保留引用,请按引用 ([&obj]捕获obj,而不是按值捕获。或者,传递包裹在std::ref中的对象。

至于参数,它们目前都是复制的(无论是在QueueFunction还是在 lambda 捕获中(。如果这不是您的意图,请参阅上文。

如果存储引用,则开头提到的所有复制操作将只复制引用(而不是对象(。