基本表达式

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 /*constexpr */ 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 /*constexpr */ int operator()(int x) const
{
return x * 2;
}

// Here
using retType_1_12 = int (*)(int);
inline constexpr operator retType_1_12 () const noexcept
{
return __invoke;
};

private:
static inline /*constexpr */ int __invoke(int x)
{
return __lambda_1_12{}.operator()(x);
}


public:
// /*constexpr */ __lambda_1_12() = default;

};

__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; // 3
std::cout << add(1.5, 2.5) << std::endl; // 4.0

捕获时初始化 (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() {
// C++20: 需要显式捕获this
return [=, this]() { return value; };
// 或者使用 [*this] 按值捕获整个对象
}
};

生命周期问题

捕获引用时需要特别注意生命周期,避免悬空引用:

1
2
3
4
5
6
7
8
9
10
11
std::function<int()> createFunc() {
int x = 42;
// 危险:x在函数返回后被销毁
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(); // Hello, World!
greet("Alice"); // Hello, 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); // x=10, y=5

可变参数模板 (C++14)

lambda支持可变参数模板:

1
2
3
4
auto printAll = [](auto&&... args) {
(std::cout << ... << args) << std::endl;
};
printAll(1, " + ", 2, " = ", 3); // 输出: 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; // 3.14
std::cout << maxValue(5, 10) << std::endl; // 10

参数包展开

可以在lambda体内展开参数包:

1
2
3
4
auto sumAll = [](auto... args) {
return (args + ...); // 折叠表达式
};
std::cout << sumAll(1, 2, 3, 4, 5) << std::endl; // 15

noexcept规范

可以为lambda指定noexcept:

1
2
3
4
auto safeDivide = [](int a, int b) noexcept -> int {
if (b == 0) return 0; // 注意:noexcept函数中抛出异常会终止程序
return a / b;
};

属性说明符 (C++11/14/17)

可以为lambda添加属性说明符:

1
2
3
4
5
6
7
8
9
// [[nodiscard]] 属性 (C++17)
auto createResource = []() [[nodiscard]] {
return std::make_unique<int>(42);
};

// [[deprecated]] 属性
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
std::function<int(int)> factorial;
factorial = [&factorial](int n) -> int {
return n <= 1 ? 1 : n * factorial(n - 1);
};

// 使用Y组合子(高级技巧)
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);
});