我应该通过 const 引用传递 lambda 吗?

Should I pass a lambda by const reference.

通常我在接受 lambda 作为函数的参数时使用以下模式(模板 class 按值传递):

template <class Function>
void higherOrderFunction(Function f) {
     f();
}

这个参数是否复制(关闭)? 如果是这样,通过 const 引用接受 lambda 有什么问题吗?

template <class Function>
void higherOrderFunction(const Function& f) {
  f();
}

一个简单的测试似乎表明这工作正常,但我想知道是否有任何我应该注意的特殊注意事项。

如果你按值传递,你将复制闭包对象(假设你没有定义 lambda 内联,在这种情况下它将被移动)。如果状态的复制成本很高,这可能是不可取的,如果状态不可复制,则编译将失败。

template <class Function>
void higherOrderFunction(Function f);

std::unique_ptr<int> p;
auto l = [p = std::move(p)] {}; // C++14 lambda with init capture
higherOrderFunction(l);         // doesn't compile because l is non-copyable 
                                // due to unique_ptr member
higherOrderFunction([p = std::move(p)] {}); // this still works, the closure object is moved

如果您通过 const 引用传递,则您不能将修改其数据成员的 mutable lambda 作为参数传递给 higherOrderFunction(),因为 mutable lambda 具有一个非 const operator(),你不能在 const 对象上调用它。

template <class Function>
void higherOrderFunction(Function const& f);

int i = 0;
higherOrderFunction([=]() mutable { i = 0; }); // will not compile

最好的选择是使用转发参考。然后 higherOrderFunction 可以接受调用者传递的左值或右值。

template <class Function>
void higherOrderFunction(Function&& f) {
     std::forward<Function>(f)();
}

这允许简单的案例以及上面提到的案例进行编译。有关为什么应使用 std::forward 的讨论,请参阅 .

Live demo

副本就是副本,所以你不能改变原件,当涉及大量数据时可能会对性能产生一些影响:

#include <iostream>
using namespace std;

template<typename Fn>
void call_value(Fn f) { f(); }
template<typename Fn>
void call_ref(Fn & f) { f(); }
template<typename Fn>
void call_cref(Fn const & f) { f(); }

struct Data {
  Data() {}
  Data(Data const &) {
    cout << "copy" << endl;
  }
  Data(Data &&) {
    cout << "move" << endl;
  }
};

int main(int, char **) {
  Data data;

  auto capref = [&data] () {};
  cout << "capture by value, so we get a ";
  auto capcp = [data] () {};

  cout << " the lambda with a reference ... ";
  call_value(capref);
  cout << " could now be called and mutate .. ";
  call_ref(capref);
  call_cref(capref);
  cout << " but won't, as it had to be declared mutable " << endl;

  cout << "the lambda with an instance: ";
  call_value(capcp);
  cout << "but not ";
  call_ref(capcp);
  call_cref(capcp);
  cout << " the reference versions " << endl;

  bool en = false;
  auto trigger = [en](bool enable = true) mutable {
    if (en) {
      cout << "fire!" << endl;
    }
    if (en or enable) {
      en = true;
    }
  };
  cout << "won't shoot" << endl;
  trigger(false);
  call_value(trigger);
  trigger(false);
  call_ref(trigger);
  cout << "and now ... ";
  trigger(false);
  // const ref won't work
  return 0;
}

看到了in action.

不要忘记:lambda 只是可调用 类 的语法糖。 (但非常有帮助)

STL 不使用模板参数中函数对象的引用。所以,很可能,你也不应该,除非你有非常具体的需求并且深入了解为什么参考会在你的情况下帮助你,以及这样做的风险是什么。

如果你使用引用,我不确定最终结果是否易于阅读和极易维护,所以即使那样你也应该找到替代解决方案,像例如在引用捕获的 var 中维护您的状态,并继续按值传递您的 lambda 对象。否则,请至少添加一些注释来说明你为什么做一些复杂和非惯用的事情,而不是标准在其自己的库中到处使用的简单事情。

无论如何,在某些地方完美转发通用引用会非常好,并且具有额外复杂性的小风险:如果函数对象只是转发 一次 到另一个函数的参数.好吧,这就是为什么你应该坚持尽可能简单的东西,除非你深入理解(并且你希望你的代码的所有未来维护者也会这样做);因为在一般情况下,盲目地在通用引用上使用 std::forward 不安全 :例如,在循环体中使用它是极其危险的。即便如此,您可能只是希望未来的维护者也知道这一点,而不是以看似简单但不正确的方式扩展代码。

所以作为一般规则;如果您非常了解为什么 使用它们、它们如何工作的所有细节以及所有相关的潜在风险,则只使用特别棘手的 C++ 功能。在 C++ 中正确使用 C++ 引用是很棘手的,因为如果你犯了任何错误(在数百个潜在错误中),编译器会很乐意为你损坏的程序生成一个二进制文件,大多数时候甚至没有警告告诉你你做了一些狗屎.按价值传递的危险性要小得多,因此至少在标准这样做的情况下,您也应该这样做。除非你是专家,而且只和专家一起工作。