C++中lambda使用mutable关键字详解
在《C++初学者指南-5.标准库(第二部分)–更改元素算法》中,讲“generate”算法时有下面这段代码:
auto gen = [i=0]() mutable { i += 2; return i; };
std::vector<int> v;
v.resize(7,0);
generate(begin(v)+1, begin(v)+5, gen);
for (int x : v) { cout << x << ' '; } // 0 2 4 6 8 0 0
第一行的lambda中使用了mutable关键字,很多人不知道其中含义,这里详细解释一下。
lambda表达式,最终会被C++编译器转换成一个匿名函数类,函数类又叫仿函数,就是重载了函数调用符:operator()的类。
可以像函数一样使用这个类的对象实例。在编译器的转换中会默认将值捕获方式的lambda转换的operator()函数的cv限定符设为const,
也就是operator()函数内是不能修改捕获的变量值的,匿名类中的重载operator()成员函数就是lambda函数本体。如果lambda表达式未使用mutable修饰,则operator()函数是const类型的,使用mutable可以解除该限制。这样就可以在lambda内修改捕获的变量值了。
我们通过Complie Explorer的反汇编功能可以清楚的看到这一点:
#include <vector>
#include <iostream>
#include <algorithm>
int main () {
auto gen = [i=0]() mutable { i=i+2; return i; };
std::cout << gen() << "\n";
}
反汇编后如下:
main::{lambda()#1}::operator()(): //注意operator()函数定义时没有const
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov eax, DWORD PTR [rax]
lea edx, [rax+2]
mov rax, QWORD PTR [rbp-8]
mov DWORD PTR [rax], edx
mov rax, QWORD PTR [rbp-8]
mov eax, DWORD PTR [rax]
pop rbp
ret
.LC0:
.string "\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 0
lea rax, [rbp-4]
mov rdi, rax
call main::{lambda()#1}::operator()()
mov esi, eax
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov eax, 0
leave
ret
我们再看一下不加mutable的代码:
#include <vector>
#include <iostream>
#include <algorithm>
int main () {
auto gen = [i=0]() { return i; };
std::cout << gen() << "\n";
}
反汇编如下:
main::{lambda()#1}::operator()() const: //注意定义函数时有const
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov eax, DWORD PTR [rax]
pop rbp
ret
.LC0:
.string "\n"
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], 0
lea rax, [rbp-4]
mov rdi, rax
call main::{lambda()#1}::operator()() const
mov esi, eax
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:.LC0
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
mov eax, 0
leave
ret
如果文章对您有用,请随手点个赞,谢谢!^_^