定义Lambda的一般指南

General guidelines for defining lambdas

本文关键字:一般指 Lambda 定义      更新时间:2023-10-16

现在我们能够在代码库中使用C++11 lambdas了,我们正在努力制定如何定义和使用它们的一般原则。我意识到这肯定有主观因素,但我认为也可能有一些对社区有用的更通用的经验法则。

定义lambda的一般原则是什么?

  • 您应该在什么时候通过引用[&]或值[=]进行捕获?性能影响是什么
  • 您应该在什么时候明确地捕获变量,例如[&foo]
  • 在什么情况下应该指定返回类型?(与C++11相比,C++14对推断返回类型有更好的支持)
  • 在将lambda重写为函数之前,它能有多复杂

就我个人而言,我目前的总体原则是"只要需要简单的谓词或比较器,就使用lambda",但这可能意味着我错过了一些更强大的用例。

这些问题有些主观,但我会尝试一下:

  • 当您需要修改封闭范围中的值时(显然),或者当您希望避免复制重变量时,通过引用捕获;否则按价值捕获。

  • 如果需要修改某个特定变量在封闭范围中的值,但不需要修改其他变量的值,请通过引用捕获该变量。

  • 我总是试图指定返回类型以增加可读性(这样其他人就可以立即知道返回类型,而不必解析lambda来推导它)。

  • 最后一个是最主观的,但我个人认为大于3-5行的lambda应该重构为函数,因为长lambda会降低可读性。然而,也可能有很多例外,所以这是个人偏好的问题,并且在很大程度上取决于实际代码。

许多问题的答案都是基于程序员的品味和代码风格。

您应该在什么时候通过引用[&]或值[=]进行捕获?性能影响是什么?

当您想要更改传递的对象或不想要进行复制时,您将通过&进行捕获。否则您可以通过=进行捕获。

 

您应该在什么时候明确地捕获变量,例如[&foo]?

如果您只想使用[&foo]捕获一个特定的对象,那么您可以通过只传递特定的对象而不是[&]来提高代码的限制性,以避免意外的错误。

 

在什么情况下应该指定返回类型?

您将在需要时确定返回类型(C++11)。没有什么特别的建议。

 

在将lambda重写为函数之前,它能有多复杂?

如果你认为一个函数在许多函数中被重用是有用的,那么你应该把它写成正常功能。Lambdas通常用于解决本地需求。

 

只要需要简单的谓词或比较器,就使用lambda

是的。谓词或比较器函数将不再被重用,这是使用lambda的常见情况。

对于您的最后一个问题:lambda函数本质上是一个内联定义的匿名函数(一个没有名称的函数)。当您需要一个似乎并不能真正证明声明和定义正常函数的合理性的小函数时,它非常有用。lambda函数很方便的典型例子是将比较传递给std::sort。例如:

struct Apple
{
   Apple(double weight, int age) :
      m_weight(weight),
      m_age(age)
   {}
   double m_weight; 
   int m_age; 
};
int main()
{
   vector<Apple> myApples;
   myApples.push_back(Apple(0.30, 30));
   myApples.push_back(Apple(0.75, 80));
   myApples.push_back(Apple(0.55, 90));
   sort(myApples.begin(),
        myApples.end(),
        [](const Apple &a, const Apple &b) -> bool
        {
           return (a.m_weight < b.m_weight);
        });
   return 0;
}

除其他答案外。

只要需要简单的谓词或比较器,就使用lambda

另一种有用的模式是初始化const变量。在lambdas之前,当变量的初始化变得过于复杂,无法容纳在单个表达式中时,您必须删除常量,或者编写一个只使用一次的单独函数(这还有其他问题,例如必须从原始范围传递参数)。

考虑以下愚蠢的、极其简化的示例(假设x的初始化不能重写为单个表达式,因为if部分太复杂):

void foo(int y) {
    int x = 42;
    if (y > 42)
        ++x;
    // from now on we mustn’t change x any more
    // but unfortunately it's not const
    // use x
}
// or
int init_x(int y) {
    // this function is only used once to initialize x in foo()
    // we could have many more parameters (not just y)
    int x = 42;
    if (y > 42)
        ++x;
    return x;
}
void foo(int y) {
    const int x = init_x(y);
    // use x
}

现在有了lambdas,你可以去掉额外的函数和参数传递,但仍然保持常量:

void foo(int y) {
    const int x = [&]() {
            int x = 42;
            if (y > 42)
                ++x;
            return x;
        }();
    //   ^^ note: we call the lambda immediately
    // use x
}

当然,这仍然受制于通常的"规则",即我们是否可以内联编写代码,或者我们应该制作一个单独的函数。但这很好地解决了初始化代码足够复杂而必须降低常量,但又不足以证明单独函数的合理性的情况。