構造閉包:可以捕獲做用域中變量的匿名函數的對象,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 起)。