标签:逆序 析构 情况 art 指令 lambda 方式 形参 怎么
构造闭包:能够捕获作用域中变量的匿名函数的对象,Lambda 表达式是纯右值表达式,其类型是独有的无名非联合非聚合类类型,被称为闭包类型(closure type),所以在声明的时候必须使用 auto
来声明。
在其它语言如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
保存起来,这也是上面例子的体现。
说明符 :
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();
&
(以引用隐式捕获被使用的自动变量)=
(以复制隐式捕获被使用的自动变量)当出现任一默认捕获符时,都能隐式捕获当前对象(this)。当它被隐式捕获时,始终被以引用捕获,即使默认捕获符是 = 也是如此。~~当默认捕获符为 = 时,(this) 的隐式捕获被弃用。 (C++20 起)~~,见this分析
捕获 中单独的捕获符的语法是
捕获列表可以不同的捕获方式,当默认捕获符是 & 时,后继的简单捕获符必须不以 & 开始, 当默认捕获符是 = 时,后继的简单捕获符必须以 & 开始,或者为 *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 起)。
标签:逆序 析构 情况 art 指令 lambda 方式 形参 怎么
原文地址:https://www.cnblogs.com/shuqin/p/12241954.html