首页 > 解决方案 > std::function - 值与参考参数

问题描述

std::function带有值参数的类型与带有 const 引用值参数的类型之间有什么实际区别吗?考虑以下代码:

auto foo = [] (VeryBigType i) {     
};

auto bar = [] (const VeryBigType& i) {
};

std::function<void(VeryBigType)> a;
a = foo;
a = bar;

std::function<void(const VeryBigType&)> b;
b = foo;
b = bar;

此代码编译没有问题并且运行良好。我知道按值传递与按引用传递有性能差异,因此foobar表现不同。但是根据std::function模板类型有什么不同吗?例如, std::function<void(VeryBigType)>(bar)vs之间是否存在任何实现和/或行为和/或性能差异,std::function<void(const VeryBigType&)>(bar)或者这些构造是等效的?

标签: c++c++11

解决方案


cppreference 说std::function<R(Args...)>::operator()签名

R operator()(Args... args) const;

f并且它基本上通过调用存储的可调用对象f(std::forward<Args>(args)...)。性能特征取决于模板参数和 lambda 的参数类型,我认为只查看可能发生的一切会很有帮助。在您的情况下,您有 2std::function种类型、2 个可调用对象和 3 个可能的参数值类别,为您提供 12 种可能性。

  • std::function<void(VeryBigType)> f = [](VeryBigType i) { }

    • 如果你用左值调用它,比如

      VeryBigType v;
      f(v);
      

      这将复制v到 的参数中operator(),然后operator()将一个右值传递给 lambda,这会将值移动到i. 总成本:1份+1招

    • 如果你用纯右值调用它,比如

      f(VeryBigType{});
      

      然后这会将纯右值具体化为 的参数operator(),然后将一个右值传递给 lambda,它会将其移动到i. 总成本:1 步

    • 如果你用一个 xvalue 来调用它,比如

      VeryBigType v;
      f(std::move(v));
      

      这将移动v到 的参数中operator(),这会将右值传递给 lambda,然后将其再次移动到i. 总成本:2 步。

  • std::function<void(VeryBigType)> f = [](VeryBigType const &i) { }

    • 如果您使用左值调用 this,则 this 将复制一次到 的参数中operator(),然后 lambda 将获得对该参数的引用。总成本:1份。

    • 如果您使用纯右值调用它,这会将其具体化为 的参数operator(),该参数会将对该参数的引用传递给 lambda。总成本:没有。

    • 如果你用一个 xvalue 调用它,这会将它移动到 的参数中operator(),这会将对该参数的引用传递给 lambda。总成本:1 步。

  • std::function<void(VeryBigType const&)> f = [](VeryBigType i) { }

    • 如果您使用左值或 xvalue(即使用左值)调用它,operator()将收到对它的引用。如果你用纯右值调用它,它将被具体化为一个临时值,并operator()会收到对它的引用。无论如何,对 lambda 的内部调用将始终复制。总成本:1份。
  • std::function<void(VeryBigType const&)> f = [](VeryBigType const &i) { }

    • 同样,无论你用什么称呼它,operator()都只会收到对它的引用,而 lambda 只会收到相同的引用。总成本:没有。

那么,我们学到了什么?如果 thestd::function和 lambda 都采用引用,则可以避免任何无关的复制和移动。尽可能使用它。然而,将一个按值的 lambda 放在一个按const--lvalue-referencestd::function中是一个坏主意(除非你必须这样做)。本质上,左值引用“忘记”了参数的值类别,并且始终复制 lambda 的参数。将 by-- constlvalue-reference lambda 放入 by-valuestd::function在性能方面非常好,但只有在调用其他需要 by-value 的代码时才需要这样做std::function,否则按引用std::function实现同样的东西,但复制和移动更少。将一个按值 lambda 放在一个按值内std::function比放入一个 -const-lvalue-reference lambda 在其中,由于所有调用中的额外移动。最好改为通过右值引用获取 lambda 的参数,这与通过const左值引用获取它几乎相同,只是你仍然可以改变参数,就像你无论如何都按值获取它一样.

std::functionTL;DR:模板参数中的按值和右值引用参数应对应于const您放入std::function. 类型中的按左值引用参数应对应于 lambda 中的按左值引用参数。其他任何东西都会产生额外的副本或移动,并且只应在需要时使用。


推荐阅读