ラムダ式の分析
閉鎖構造:オブジェクトそれ匿名関数を捕捉することができる可変領域、ラムダ式は、合成非ポリマー非匿名クラス型のユニークなタイプであり、純粋な右値の表現、である、式クロージャ(閉鎖型)と呼ばれます、その宣言はしなければならない時にauto
宣言されます。
例えばLUAのような他の言語では、クロージャのフォーマットは、すべての変数スコープラムダ式、および戻りクロージャを使用することができ、比較的単純です
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 ++では、それは複雑でなく、プロパティの閉鎖機能を制御するための追加機能を提供します表示されます。
ラムダ和のstd ::機能
呼び出しラムダ使用および機能のオブジェクトは類似しているが、
std::function<int(int, int)> add2 = [&](int a, int b) -> int {
return a + b + val + f1.value;
};
しかし、彼らは同じものではありません、型付きラムダは、(コンパイル時に決定)不可知である、使用してsizeof
2の大きさは同じではありませんが、std::function
ヘビーデューティータイプ排除することによって、そのオブジェクトの関数であり、そしてoperator()
長い。この機能のように、通話に影響を呼び出すことができる条件を満たすためには、使用することができますstd::function
保存され、これは上記の例に反映されています。
構文C ++ 17
- [キャプチャ](パラメータ)指定子(オプション)説明例外- > RET {}関数本体
- 全量声明
- [キャプチャ](パラメータ) - > RET {}関数本体
- constのラムダ計算書、コピーキャプチャラムダ本体内のオブジェクトはconstのです
- [キャプチャ](パラメータ){}関数本体
- 戻り型宣言を省略し、関数リターン部材の戻り型に由来します
- [キャプチャ] {}関数本体
- 関数は、引数がありません
指定子:
mutable
許可関数本体が各反復キャプチャのパラメータを変更しますconstexpr
C ++ 17には、明示的に機能アプリケーションとして指定されたconstexpr
機能を満たすとき、constexpr
また、明示的に指定しない場合でも、要件の時間関数であり、constexpr
例外の説明:提供throw
やnoexpect
言葉
次のように使用します。
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();
ラムダ・キャプチャー
&
(暗黙の参照変数は、自動的に捕捉するために使用されます)=
(暗黙の変数が使用されているコピーするために自動的に取得)
デフォルトのキャプチャ任意の文字がある場合、あなたは暗黙的に(現在のオブジェクトをキャプチャすることができ、これを)。それは暗黙的に取得された場合、参照は常に文字がデフォルト=真によって捕獲された場合でも、撮影しています。〜デフォルトの文字がキャプチャされる=、(この)暗黙のキャプチャは非推奨。(以降C ++ 20)〜を参照してください。この分析
キャプチャ構文キャプチャ個々の文字があります
- 識別子
- キャプチャをコピーするのは簡単
- 識別子...
- シンプルなキャプチャをコピーするための拡張パックとして、
- 識別子初期化子
- 複製キャプチャに初期化して
- &識別子
- 単純な参照キャプチャ
- &識別子...
- 簡単な見積もりをキャプチャするための拡張パックとして、
- &識別子初期化子
- 初期化子のキャプチャを参照して
- この
- キャプチャへの単純なオブジェクト参照電流
- * この
- キャプチャに現在のオブジェクトの単純なコピー、C ++ 17
デフォルトのキャプチャ文字があると、その後の簡単なキャプチャオペレータは(しないように&を開始し、デフォルトのキャプチャ文字である=、その後の簡単なキャプチャオペレータ必見なければならない&開始、または*このとき、キャプチャリストは、異なるキャプチャモードもありC ++ 17)またはこの(C ++ 20以降)からです。
上記の例では、コードの一部として主の増加、二つ取得モードを含む、およびインビボ機能に捕捉可変ラムダの修正値、及び、オブジェクトを返します
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);
コンパイラがどのように実装されるか確認するために、解体ラムダ式を愛したの状況に行ってきました。
_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、コンパイラが生成されたコードの順序は、より混乱さ、多くの作業を行っています
- 利用
=
価値キャプチャは、それが最初のコピーコンストラクタを呼び出します。 - 使用
&
隠されたパラメータとして無名関数へのキャプチャ、キャプチャオブジェクト参照(アドレス)を参照するとき - コンパイラが生成のみデストラクタがあるだろう、無名関数を生成しないであろう、この関数は無名関数のデストラクタに呼び出すための責任があります
ライフサイクル
オブジェクトのライフサイクルに関連するラムダ式は、解体上記参照します:
- グローバル、ライフサイクルのより多くの外側の範囲は影響を受けません
- デストラクタを実行した後関数のボディの後、前に構築されたオブジェクトラムダ関数本体にキャプチャされた値を使用して
- 機能が閉鎖デストラクタデストラクタに、インビボで対象体ラムダ式の関数で実行されるときに作成
- ラムダは、スコープのオブジェクトの端部であるライフサイクルは、デストラクタは、逆の順序のデストラクタを宣言しました
この
生成されたアセンブリコードを使用して-std = C ++ 14は=
、&
、this
捕捉の場合は、生産アセンブリコードは、ほとんど使用されている送信パラメータの同じ、参照(このアドレス)であり、ケースの-std = C ++ 2aの使用コンパイラは、値のキャプチャモードを使用することを推奨していません(参照キャプチャまたは使用しましたが)。
ALL
- パラメータパッケージの解析が完了しました
参照
ラムダ式、cppreferenceラムダ式(C ++ 11以降)。