基本表达式 1 [capture](parameters) -> return -type {body}
当没有返回类型时, 可以省略 -> return-type
变量捕获与lambda闭包实现 [] 不截取任何变量[&] 截取外部作用域中所有变量,并作为引用在函数体中使用[=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用[=, &foo] 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用[bar] 截取bar变量并且拷贝一份在函数体中使用,同时不截取其他变量[x, &y] x按值传递,y按引用传递[this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。lambda 的底层实现 lambda 其实是c++的语法糖,是通过c++编译器生成class来实现的,看一个简单的例子。
1 2 3 4 5 6 7 8 9 10 11 12 #include <functional> using namespace std;int main (int argc, char * argv[]) { int x = 0 ; int y = 0 ; int z = 0 ; auto func = [&](int a)->int {x+=a; y++; return x;}; func (1 ); return 0 ; }
通过cppinsights 站点来查看编译器的实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <functional> using namespace std;int main (int argc, char ** argv) { int x = 0 ; int y = 0 ; int z = 0 ; class __lambda_8_16 { public : inline int operator () (int a) const { x = x + a; y++; return x; } private : int & x; int & y; public : __lambda_8_16(int & _x, int & _y) : x{_x}, y{_y} {} }; __lambda_8_16 func = __lambda_8_16{x, y}; static_cast <const __lambda_8_16>(func).operator ()(1 ); return 0 ; }
匿名函数在编译器中生成了类 class __lambda_6_16,其中operator()中使用了x和y,参数是引用传参。
再来看一个没有变量捕获的例子:
1 auto lam = [](int x) { return x * 2 ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class __lambda_1_12 { public : inline int operator () (int x) const { return x * 2 ; } using retType_1_12 = int (*)(int ); inline constexpr operator retType_1_12 () const noexcept { return __invoke; }; private : static inline int __invoke(int x) { return __lambda_1_12{}.operator ()(x); } public : }; __lambda_1_12 lam = __lambda_1_12{};
和有变量捕获的 lambda 的最大差异是有 operator func_type() 函数,也就是说可以转为函数指针。
lambda 表达式中需要注意的事项 mutable 关键字 默认情况下,按值捕获的变量在lambda函数体内是const的,不能修改。使用mutable关键字可以移除这个const限制:
1 2 3 4 5 int x = 0 ;auto func = [x]() mutable { x++; return x; };
泛型lambda (C++14) C++14引入了泛型lambda,可以使用auto作为参数类型:
1 2 3 auto add = [](auto a, auto b) { return a + b; }; std::cout << add (1 , 2 ) << std::endl; std::cout << add (1.5 , 2.5 ) << std::endl;
捕获时初始化 (C++14) 可以在捕获列表中初始化变量,这对于捕获只能移动的类型特别有用:
1 2 3 4 std::unique_ptr<int > ptr = std::make_unique <int >(42 );auto func = [value = std::move (ptr)]() { return *value; };
模板lambda (C++20) C++20引入了模板lambda,可以使用模板语法:
1 2 auto func = []<typename T>(T a, T b) { return a + b; }; std::cout << func (1 , 2 ) << std::endl;
constexpr lambda (C++17) C++17允许lambda在编译时求值:
1 2 constexpr auto square = [](int n) { return n * n; };static_assert (square (5 ) == 25 );
this捕获的变化 (C++20) C++20中,[=]不再隐式捕获this,需要显式捕获:
1 2 3 4 5 6 7 8 9 class MyClass { int value = 42 ;public : auto getFunc () { return [=, this ]() { return value; }; } };
生命周期问题 捕获引用时需要特别注意生命周期,避免悬空引用:
1 2 3 4 5 6 7 8 9 10 11 std::function<int () > createFunc () { int x = 42 ; return [&x]() { return x; }; }std::function<int () > createFuncSafe () { int x = 42 ; return [x]() { return x; }; }
性能考虑 lambda通常会被编译器内联,性能接近普通函数 避免在性能关键路径中使用复杂的捕获 小lambda适合作为算法参数,如std::sort的比较函数 与std::bind的比较 优先使用lambda而不是std::bind,因为:
lambda语法更清晰 编译器优化更好 类型安全更强 C++14后lambda功能更强大 转换为函数指针 无捕获的lambda可以隐式转换为函数指针:
1 2 3 4 5 6 7 8 void callFunc (int (*func)(int )) { std::cout << func (5 ) << std::endl; }int main () { auto lambda = [](int x) { return x * 2 ; }; callFunc (lambda); }
lambda参数特性 lambda的参数语法与普通函数类似,但有一些特殊之处:
默认参数 lambda支持默认参数,但需要注意使用场景:
1 2 3 4 5 auto greet = [](const std::string& name = "World" ) { std::cout << "Hello, " << name << "!" << std::endl; };greet (); greet ("Alice" );
引用参数和const引用 可以像普通函数一样使用引用参数:
1 2 3 4 5 6 7 8 auto swapValues = [](int & a, int & b) { int temp = a; a = b; b = temp; };int x = 5 , y = 10 ;swapValues (x, y);
可变参数模板 (C++14) lambda支持可变参数模板:
1 2 3 4 auto printAll = [](auto &&... args) { (std::cout << ... << args) << std::endl; };printAll (1 , " + " , 2 , " = " , 3 );
参数类型推导 C++14引入的泛型lambda可以自动推导参数类型:
1 2 3 4 5 6 auto maxValue = [](auto a, auto b) { return a > b ? a : b; }; std::cout << maxValue (3.14 , 2.71 ) << std::endl; std::cout << maxValue (5 , 10 ) << std::endl;
参数包展开 可以在lambda体内展开参数包:
1 2 3 4 auto sumAll = [](auto ... args) { return (args + ...); }; std::cout << sumAll (1 , 2 , 3 , 4 , 5 ) << std::endl;
noexcept规范 可以为lambda指定noexcept:
1 2 3 4 auto safeDivide = [](int a, int b) noexcept -> int { if (b == 0 ) return 0 ; return a / b; };
属性说明符 (C++11/14/17) 可以为lambda添加属性说明符:
1 2 3 4 5 6 7 8 9 auto createResource = []() [[nodiscard]] { return std::make_unique <int >(42 ); };auto oldFunc = []() [[deprecated ("Use newFunc instead" )]] { return 42 ; };
递归lambda lambda不能直接递归调用自己,需要使用std::function或Y组合子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 std::function<int (int )> factorial; factorial = [&factorial](int n) -> int { return n <= 1 ? 1 : n * factorial (n - 1 ); };auto y = [](auto f) { return [=](auto ... args) { return f (f, args...); }; };auto factorial2 = y ([](auto self, int n) -> int { return n <= 1 ? 1 : n * self (self, n - 1 ); });