构造闭包:可以捕获做用域中变量的匿名函数的对象,Lambda 表达式是纯右值表达式,其类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type),因此在声明的时候必须使用 auto
来声明。c++
在其它语言如lua中,闭包的格式相对更为简单,可使用 lambda 表达式做用域的全部变量,而且返回闭包闭包
local function add10(arg) local i = 10 local ret = function() i = i - 1 return i + arg end return ret end print( add10(1)() ) -- 10
C++ 中则显得复杂些,也提供了更多的功能来控制闭包函数的属性。函数
虽然 lambda 的使用和函数对象的调用方式有类似之处,优化
std::function<int(int, int)> add2 = [&](int a, int b) -> int { return a + b + val + f1.value; };
但他们并非同一种东西,lambda 的类型是不可知的(在编译期决定),使用 sizeof
二者的大小也是不相同的,std::function
是函数对象,经过消除类型再重载 operator()
达到调用的效果,只要这个函数知足能够调用的条件,就可使用std::function
保存起来,这也是上面例子的体现。this
说明符 :lua
mutable
, 容许 函数体 修改各个复制捕获的形参constexpr
C++ 17, 显式指定函数调用符为 constexpr
,当函数体知足 constexpr
函数要求时,即便未显式指定,也会是 constexpr
异常说明 :提供 throw
或者 noexpect
字句spa
使用以下:指针
struct Foo { int value; Foo() : value(1) { std::cout << "Foo::Foo();\n"; } Foo(const Foo &other) { value = other.value; std::cout << "Foo::Foo(const Foo &)\n"; } ~Foo() { value = 0; std::cout << "Foo::~Foo();\n"; } }; int main() { int val = 7; Foo f1; auto add1 = [&](int a, int b) mutable noexcept->int { return a + b + val + f1.value; }; // 使用 std::function 包装 std::function<int(int, int)> add2 = [&](int a, int b) -> int { f1.value = val; // OK,引用捕获 return a + b + val + f1.value; }; auto add3 = [&](int a, int b) { return a + b + val + f1.value; }; auto add4 = [=] { // f1.value = val; // 错误,复制捕获 的对象在 lambda 体内为 const return val + f1.value; }; // 全 auto 也是能够,返回的这个 auto 不写也行 auto add5 = [=](auto a, int b) -> auto { return a + b; }; } // 输出: Foo::Foo(); Foo::Foo(const Foo &) Foo::~Foo(); Foo::~Foo();
&
(以引用隐式捕获被使用的自动变量)=
(以复制隐式捕获被使用的自动变量)当出现任一默认捕获符时,都能隐式捕获当前对象(this)。当它被隐式捕获时,始终被以引用捕获,即便默认捕获符是 = 也是如此。~~当默认捕获符为 = 时,(this) 的隐式捕获被弃用。 (C++20 起)~~,见this分析
捕获 中单独的捕获符的语法是code
捕获列表能够不一样的捕获方式,当默认捕获符是 & 时,后继的简单捕获符必须不以 & 开始, 当默认捕获符是 = 时,后继的简单捕获符必须以 & 开始,或者为 *this (C++17 起) 或 this (C++20 起).对象
在上面的示例main中增长,部分代码以下,包括了两种捕获方式,及在函数体内修改lambda捕获变量的值,及返回对象
Foo f1; Foo f2; int val = 7; auto add6 = [=, &f2](int a) mutable { f2.value *= a; f1.value += f2.value + val; return f1; }; Foo f3 = add6(3);
又到了喜闻乐见反汇编的状况了,看看编译器是怎么实现的lambda表达式的。
_ZZ4mainENUliE_clEi: .LFB10: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $32, %rsp movq %rdi, -8(%rbp) movq %rsi, -16(%rbp) movl %edx, -20(%rbp) // int a movq -16(%rbp), %rax // -16(%rbp) = & this(f2),每次都这么赋值,没优化的指令真的很冗余 movq (%rax), %rax movl (%rax), %edx // %edx = f2.value movq -16(%rbp), %rax movq (%rax), %rax imull -20(%rbp), %edx // %edx = f2.value * a movl %edx, (%rax) // f2.value = %edx movq -16(%rbp), %rax movl 8(%rax), %edx // 在main函数中 -32(%rbp) + 8 = -24(%rbp) 也就是copy构造函数产生的 this 指针 movq -16(%rbp), %rax // 如下的就是那些加减了, movq (%rax), %rax movl (%rax), %ecx movq -16(%rbp), %rax movl 12(%rax), %eax addl %ecx, %eax addl %eax, %edx movq -16(%rbp), %rax movl %edx, 8(%rax) movq -16(%rbp), %rax leaq 8(%rax), %rdx movq -8(%rbp), %rax movq %rdx, %rsi // 上一个copy构造函数内的 this 指针 movq %rax, %rdi // copy构造的this指针 call _ZN3FooC1ERKS_ // 继续调用copy构造函数,返回 movq -8(%rbp), %rax leave .cfi_def_cfa 7, 8 ret .cfi_endproc // lambda 的析构函数,这个函数是隐式声明的 _ZZ4mainENUliE_D2Ev: .LFB12: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movq %rdi, -8(%rbp) movq -8(%rbp), %rax addq $8, %rax movq %rax, %rdi call _ZN3FooD1Ev nop leave .cfi_def_cfa 7, 8 ret .cfi_endproc main: .LFB9: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $48, %rsp movl $7, -4(%rbp) // int val = 7; leaq -8(%rbp), %rax // -8(%rbp) = this(f1) movq %rax, %rdi call _ZN3FooC1Ev // Foo f1; leaq -12(%rbp), %rax // -12(%rbp) = this(f2) movq %rax, %rdi call _ZN3FooC1Ev // Foo f2; leaq -12(%rbp), %rax movq %rax, -32(%rbp) // -32(%rbp) = this(f2) leaq -8(%rbp), %rax // 取 this(f1) leaq -32(%rbp), %rdx addq $8, %rdx // copy 构造函数的 this = -24(%rbp),记住这个 24 movq %rax, %rsi // 第二个参数 this(f1) movq %rdx, %rdi // 第一个参数,调用copy构造函数的 this call _ZN3FooC1ERKS_ // Foo(const Foo &); movl -4(%rbp), %eax movl %eax, -20(%rbp) // -20(%rbp) = 7 leaq -36(%rbp), %rax leaq -32(%rbp), %rcx movl $3, %edx movq %rcx, %rsi // 第二个参数 this(f2) 的地址(两次 leaq) movq %rax, %rdi // 须要返回的 Foo 对象的 this 指针 call _ZZ4mainENUliE_clEi // lambda 的匿名函数 leaq -36(%rbp), %rax movq %rax, %rdi call _ZN3FooD1Ev leaq -32(%rbp), %rax movq %rax, %rdi call _ZZ4mainENUliE_D1Ev // 析构函数 leaq -12(%rbp), %rax movq %rax, %rdi call _ZN3FooD1Ev leaq -8(%rbp), %rax movq %rax, %rdi call _ZN3FooD1Ev movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc
上面的汇编代码相对cpp代码仍是比较多的,因为一些隐含规则的约束下,编译器作了不少的工做,产生的代码的顺序就比较混乱
=
值捕获时,会先调用copy构造函数&
引用捕获时,将捕获对象的引用(地址)做为隐式参数传给匿名函数lambda表达式相关的对象的生命周期,见上反汇编:
使用 -std=c++14 生成的汇编代码在 =
,&
,this
捕获的状况下,产生的汇编代码几乎同样,都是使用的引用(this地址)传参,使用 -std=c++2a 的状况下,编译器不推荐使用值捕获的方式(虽然仍是使用的引用捕获)。
lambda 表达式,cppreference Lambda 表达式 (C++11 起)。