cppreference 表示 std::function<R(Args...)>::operator()
具有以下签名:
R operator()(Args... args) const
它调用存储的可调用对象f
,基本上是通过f(std::forward<Args>(args)...)
来实现的。性能特征取决于模板参数和lambda的参数类型,我认为只要看到所有可能发生的情况就会有所帮助。在您的情况下,您有2个std::function
类型、2个可调用对象和3种可能的值类别作为参数,共有12种可能性。
std::function<void(VeryBigType)> f = [](VeryBigType i) { }
If you call this with an lvalue, like
VeryBigType v;
f(v);
This will copy v
into the argument of operator()
, and then operator()
will pass an rvalue to the lambda, which will move the value into i
. Total cost: 1 copy + 1 move
If you call this with a prvalue, like
f(VeryBigType{});
Then this will materialize the prvalue into the argument of operator()
, then pass an rvalue to the lambda, which will move it into i
. Total cost: 1 move
If you call this with an xvalue, like
VeryBigType v;
f(std::move(v));
This will move v
into the argument of operator()
, which will pass an rvalue to the lambda, which will move it again into i
. Total cost: 2 moves.
std::function<void(VeryBigType)> f = [](VeryBigType const &i) { }
If you call this with an lvalue, this will copy once into the argument of operator()
, and then the lambda will be given a reference to that argument. Total cost: 1 copy.
If you call this with a prvalue, this will materialize it into the argument of operator()
, which will pass a reference to that argument to the lambda. Total cost: nothing.
If you call this with an xvalue, this will move it into the argument of operator()
, which will pass a reference to that argument to the lambda. Total cost: 1 move.
std::function<void(VeryBigType const&)> f = [](VeryBigType i) { }
- If you call this with an lvalue or xvalue (i.e. with a glvalue),
operator()
will receive a reference to it. If you call this with a prvalue, it will be materialized into a temporary, and operator()
will receive a reference to that. In any case, the inner call to the lambda will always copy. Total cost: 1 copy.
std::function<void(VeryBigType const&)> f = [](VeryBigType const &i) { }
- Again, no matter what you call this with,
operator()
will receive just a reference to it, and the lambda will just receive the same reference. Total cost: nothing.
所以,我们学到了什么?如果
std::function
和lambda表达式都采用引用方式,你可以避免任何额外的复制和移动。尽可能地使用这种方法。然而,将按值lambda放在按-
const
-左值引用的
std::function
中是个坏主意(除非你必须这样做)。实质上,左值引用“忘记”了参数的值类别,lambda表达式的参数总是被复制。将一个按-
const
-左值引用的lambda放在一个按值传递的
std::function
中在性能上相当不错,但只有在调用其他期望按值传递的代码时才需要这样做,否则按引用传递的
std::function
能够以更少的复制和移动完成相同的工作。将按值lambda放入按值
std::function
中比将按-
const
-左值引用的lambda放入其中稍微差一些,因为所有调用都会有一个额外的移动。最好的方法是使用按右值引用获取lambda的参数,这基本上与使用按-
const
-左值引用获取参数相同,除了你仍然可以像按值方式一样改变参数。
简而言之,在std::function
模板参数中按值和右值引用的参数应与放入其中的lambda表达式的按右值引用或按-const
-左值引用的参数相对应。在类型中按左值引用的参数应与lambda表达式中的按左值引用的参数相对应。其他任何方式都会增加额外的复制或移动,仅在需要时才应使用。
cdecl
,但同样适用于stdcall
(相同,但函数清理堆栈),fastcall
(使用寄存器而不是堆栈)和thiscall
(在 x86 上与cdecl
相同,在 x64 上与fastcall
相同,但它还使用ECX
/RCX
发送this
)。 - Blindystd::function
是如何工作的。 - HTNW