C++ Lambda
Lambda函数是 Modern C++在C++11中的一个体现 。

什么是lambda函数?

本文标题有点误导。因为 lambda并不总是函数指针,它一个表达式。但是为了简单起见,一直都称它为函数。
Lambda函数是一段简短的代码片段,它具有以下特点:
(1)不值得命名(无名,匿名,可处理等,无论您如何称呼)
(2)不会重复使用
换句话说,它只是语法糖。lambda函数的语法定义为:

  1. [ capture list ] (parameters) -> return-type
  2. {
  3. method definition
  4. }

通常, 编译器会评估lambda函数本身的返回类型。因此,不需要显式指定返回类型,即 -> return-type,但是在某些复杂的情况下,编译器无法推断出返回类型,此时需要指定返回类型。

为什么要使用lambda函数?

C++包括许多有用的通用函数,例如 std::for_each,通常情况下方便了使用。但是,当需要考虑特殊需求的时候,就比较头疼了。如下代码所示:

  1. struct print
  2. {
  3. void operator()(int element)
  4. {
  5. cout <<element << endl;
  6. }
  7. };
  8. int main(void)
  9. {
  10. std::vector<int> v = {1, 2, 3, 4, 5};
  11. std::for_each(v.begin(),v.end(), print());
  12. return 0;
  13. }

如果只在某个特定位置使用一次print,却写了一个类。这样的操作代价似乎太大了。
但是,对于这种情况,内联代码将更合适,并且可以通过如下的lambda函数来实现:

  1. std::for_each(v.begin(), v.end(), [](int element) { cout <<element << endl; });

lambda函数工作原理

  1. [&i] ( ) { std::cout << i; }
  2. // is equivalent to
  3. struct anonymous
  4. {
  5. int &m_i;
  6. anonymous(int &i) :m_i(i) {}
  7. inline autooperator()()const
  8. {
  9. std::cout << i;
  10. }
  11. };

如代码所示,编译器为每一个lambda函数生成独特的闭合。捕获列表将成为闭包中的构造函数参数,如果按值捕获,则会在闭包中创建相应类型的数据成员。此外,可以在lambda函数参数中声明变量/对象,它将成为调用运算符的参数,即operator()。

使用Lambda函数的好处

(1)零成本抽象。lambda不会牺牲性能,运行速度和普通函数一样快。
(2)此外,代码会变得更加紧凑,结构层次更加明显和代码可读性更佳。

学习lambda表达式

1.通过引用/值捕获

  1. int main()
  2. {
  3. int x = 100, y = 200;
  4. auto print = [&]{ // Capturing object by reference
  5. std::cout <<__PRETTY_FUNCTION__ << " : " << x<< " , " << y << std::endl;
  6. };
  7. print();
  8. return 0;
  9. }

输出:

  1. main()::<lambda()> : 100 , 200

在上面的示例中,在捕获列表中用到了 &。通过引用的方式捕获变量 xy。同样,= 表示按值捕获,这将在闭包内创建相同类型的数据成员,同时赋上相同的值。需要注意的是,参数列表是可选的, 如果不将参数传递给lambda表达式,则可以省略空括号。

2.Lambda捕获列表

2.1 将lambda作为参数传递

  1. template <typename Functor>
  2. void f(Functor functor)
  3. {
  4. std::cout <<__PRETTY_FUNCTION__ << std::endl;
  5. }
  6. /* Or alternatively you can use this
  7. void f(std::function<int(int)> functor)
  8. {
  9. std::cout <<__PRETTY_FUNCTION__ << std::endl;
  10. }
  11. */
  12. int g() { static int i = 0; return i++; }
  13. int main()
  14. {
  15. auto lambda_func =[i = 0]() mutable { return i++; };
  16. f(lambda_func); // Pass lambda
  17. f(g); // Pass function
  18. }

输出:

  1. Function Type : void f(Functor) [with Functor = main()::<lambda(int)>]
  2. Function Type : void f(Functor) [with Functor = int (*)(int)]

也可以将lambda函数作为参数传递给其他函数,就像上面编写的普通函数一样。在捕获列表中声明了变量i,它将成为数据成员。所以,每次调用lambda_func时,它将被返回并递增。

2.2lambda捕获this指针或成员变量

  1. class Example
  2. {
  3. public:
  4. Example() : m_var(10) {}
  5. void func()
  6. {
  7. [=]() { std::cout << m_var<< std::endl; }(); // IIFE
  8. }
  9. private:
  10. int m_var;
  11. };
  12. int main()
  13. {
  14. Example e;
  15. e.func();
  16. }

捕获this指针也可以使用 [this][=]或者 [&]。在上述任何情况下,类内数据成员(包括 private)的访问方式与常规方法一样。
可以看到lambda表达式的末尾,多写了一个 ( ),该函数通常在声明之后立刻对其进行调用。它称为IIFE(立即调用函数表达式)。

C++ lambda函数类型

1.通用lambda

  1. const auto l = [](auto a, auto b, auto c) {};
  2. // is equivalent to
  3. struct anonymous
  4. {
  5. template <class T0, class T1, class T2>
  6. auto operator()(T0 a, T1 b, T2 c) const
  7. {
  8. }
  9. };

C++ 14中引入的通用lambda可以使用auto说明符。

2.可变参数通用λ

  1. void print() {}
  2. template <typename First, typename... Rest>
  3. void print(const First &first, Rest &&... args)
  4. {
  5. std::cout << first<< std::endl;
  6. print(args...);
  7. }
  8. int main()
  9. {
  10. auto variadic_generic_lambda = [](auto... param) {
  11. print(param...);
  12. };
  13. variadic_generic_lambda(1, "lol", 1.1);
  14. }

具有可变参数的Lambda在许多情况下非常有用,例如调试,使用不同的数据输入重复操作等。

3.mutable lambda函数

通常,lambda的函数调用运算符是const-by-value,这意味着lambda需要捕获可变值的 关键字时,需要使用mutable关键字。

  1. []() mutable {}
  2. // is equivalentto
  3. struct anonymous
  4. {
  5. auto operator()() // call operator
  6. {
  7. }
  8. };

4.Lambda作为函数指针

  1. #include<iostream>
  2. #include<type_traits>
  3. int main()
  4. {
  5. auto funcPtr = +[] {};
  6. static_assert(std::is_same<decltype(funcPtr), void (*)()>::value);
  7. }

可以通过添加命令“+”来强制编译器将lambda生成为函数指针而不是闭包。

5.lambda函数作为返回值

  1. const auto less_than = [](auto x) {
  2. return [x](auto y) {
  3. return y < x;
  4. };
  5. };
  6. int main(void)
  7. {
  8. auto less_than_five = less_than(5);
  9. std::cout << less_than_five(3) << std::endl;
  10. std::cout << less_than_five(10) << std::endl;
  11. return 0;
  12. }

再进一步,lambda函数还可以返回另一个lambda函数。这也为代码的自定义性,代码可读性和紧凑性带来无限可能。

6.constexpr lambda表达式

从C ++ 17开始,可以将lambda表达式声明为 constexpr

  1. constexpr auto sum = [](const auto &a, const auto &b) { return a + b; };
  2. /*
  3. is equivalent to
  4. constexpr struct anonymous
  5. {
  6. template <class T1, class T2>
  7. constexpr auto operator()(T1 a, T2 b)const
  8. {
  9. return a + b;
  10. }
  11. };
  12. */
  13. constexpr int answer = sum(10, 10);

即使没有指定 constexpr,如果它恰好满足所有 constexpr函数的要求,那么它也会被声明为constexpr