C++之Lambda研究
目录
目录 1
1. 前言 1
2. 示例1 1
3. 示例2 2
4. 示例3 3
5. 示例4 3
6. 示例5 6
7. 匿名类规则 6
8. 参考资料 7
1. 前言
本文代码测试环境为“GCC-9.1.0”,有关编译器的安装请参考《安装GCC-8.3.0及其依赖》,适用于“GCC-9.1.0”。
本文试图揭露Lambda背后一面,以方便更好的理解和掌握Lambda。Lambda代码段实际为一个编译器生成的类的“operator ()”函数,编译器会为每一个Lambda函数生成一个匿名的类(在C++中,类和结构体实际一样,无本质区别,除了默认的访问控制)。
对Lambda的最简单理解,是将它看作一个匿名类(或结构体),实际上也确实如此,编译器把Lambda编译成了匿名类。
2. 示例1
先看一段几乎最简单的Lambda代码:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> int main() { auto f = [] { printf("f\n"); }; // 注意“}”后的“;”必不可少,否则编译报错 return 0; } |
如果Lambda表达式(或函数)没有以“;”结尾,则编译时将报如下错误:
a3.cpp: In function 'int main()': a3.cpp:4:3: error: expected ',' or ';' before 'return' 4 | return 0; | ^~~~~~ |
Lambda之所以神奇,这得益于C++编译器的工作,上述“f”实际长这样:
type = struct <lambda()> { } |
一个匿名的类(或结构体),实际上还有一个成员函数“operator () const”。注意这里成员函数是”const”类型,这是默认的。如果需非”const”成员函数,需要加”mutable”修饰,如下所示:
auto f = [n]() mutable { printf("%d\n", n); }; |
上面例子对应的匿名类没有任何类数据成员,现在来个有类数据成员的代码:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> int main() { int n = 3; auto f = [n] { printf("%d\n", n); }; f(); // 这里实际调用的是匿名类的成员函数“operator ()” return 0; } |
这时,“f”实际长这样,它是一个含有类数据成员的匿名类,而不再是空无一特的类:
type = struct <lambda()> { int __n; } |
3. 示例2
继续来个变种:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> int main() { int n = 3; auto f = [&n]() mutable { printf("%d\n", n); }; f(); return 0; } |
这时,“f”实际长这样,一个包含了引用类型的匿名类:
type = struct <lambda()> { int &__n; } |
4. 示例3
继续变种,“&”的作用让Lambda函数可使用Lambda所在作用域内所有可见的局部变量(包括Lambda所在类的this),并且是以引用传递方式:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> int main() { int n = 3; auto f = [&]() mutable { printf("%d\n", n); }; f(); return 0; } |
“f”实际长这样:
type = struct <lambda()> { int &__n; } |
变稍复杂一点:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> int main() { int n = 3; int m = 5; auto f = [&]() mutable { printf("%d\n", n); }; f(); return 0; } |
可以看到,“f”并没有发生变化:
type = struct <lambda()> { int &__n; } |
5. 示例4
继续增加复杂度:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> int main() { int n = 3; int m = 5; auto f = [&]() mutable { printf("%d,%d\n", n, m); }; f(); return 0; } |
可以看到“f”变了:
type = struct <lambda()> { int &__n; int &__m; } |
从上面不难看出,编译器只会把Lambda函数用到的变量打包进对应的匿名类。继续一个稍复杂点的:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> struct X { void foo() { printf("foo\n"); } void xoo() { auto f = [&] { foo(); }; f(); } }; int main() { X().xoo(); return 0; } |
这时,“f”实际长这样:
type = struct X::<lambda()> { X * const __this; // X类型的指针(非对象) } |
如果将“auto f = [&] { foo(); };”中的“&”去掉,则会遇到编译错误,提示“this”没有被Lambda函数捕获:
a2.cpp: In lambda function: a2.cpp:5:23: error: 'this' was not captured for this lambda function 5 | auto f = [] { foo(); }; | ^ a2.cpp:5:23: error: cannot call member function 'void X::foo()' without object |
改成下列方式捕获也是可以的:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> struct X { void foo() { printf("foo\n"); } void xoo() { auto f = [this] { foo(); }; f(); } }; int main() { X().xoo(); return 0; } |
如果是C++17,还可以这样:
// g++ -g -o a1 a1.cpp -std=c++17 #include <stdio.h> struct X { void foo() { printf("foo\n"); } void xoo() { auto f = [*this]() mutable { foo(); }; f(); } }; int main() { X().xoo(); return 0; } |
注意得有“mutable”修饰,不然报如下编译错误:
a2.cpp: In lambda function: a2.cpp:5:30: error: passing 'const X' as 'this' argument discards qualifiers [-fpermissive] 5 | auto f = [*this]() { foo(); }; | ^ a2.cpp:3:8: note: in call to 'void X::foo()' 3 | void foo() { printf("foo\n"); } | ^~~ |
也可以这样:
// g++ -g -o a1 a1.cpp -std=c++17 #include <stdio.h> struct X { void foo() { printf("foo\n"); } void xoo() { auto f = [&,*this]() mutable { foo(); }; f(); } }; int main() { X().xoo(); return 0; } |
使用“*this”时的“f”样子如下:
type = struct X::<lambda()> { X __this; // X类型的对象(非指针) } |
6. 示例5
继续研究,使用C++ RTTI(Run-Time Type Identification,运行时类型识别)设施“typeid”查看Lambda函数:
// g++ -g -o a1 a1.cpp -std=c++11 #include <stdio.h> #include <typeinfo> struct X { void xoo() { auto f = [] { printf("f\n"); }; printf("%s\n", typeid(f).name()); // 注:typeid返回值类型为“std::type_info” } }; int main() { X().xoo(); return 0; } |
运行输出:
ZN1X3xooEvEUlvE_ |
7. 匿名类规则
编译器为Lambda生成的匿名类规则(不同标准有区别):
构造函数 拷贝构造函数 |
ClosureType() = delete; |
C++14前 |
ClosureType() = default; |
C++20起, 仅当未指定任何俘获时 |
|
ClosureType(const ClosureType& ) = default; |
C++14起 |
|
ClosureType(ClosureType&& ) = default; |
C++14起 |
|
拷贝复制函数 |
ClosureType& operator=(const ClosureType&) = delete; |
C++20前 |
ClosureType& operator=(const ClosureType&) = default; ClosureType& operator=(ClosureType&&) = default; |
C++20起, 仅当未指定任何俘获时 |
|
ClosureType& operator=(const ClosureType&) = delete; |
C++20起,其他情况 |
|
析构函数 |
~ClosureType() = default; |
析构函数是隐式声明的 |
对于标记为“delete”的函数是不能调用的,如下列代码中的“f2 = f1;”将触发编译错误:
int main() { auto f1 = []{}; auto f2 = f1; f2 = f1; return 0; } |
上列代码在C++11、C++14和C++17均会报错。不过如规则所示,C++20(含C++2a)上则可以正常编译:
a3.cpp: In function 'int main()': a3.cpp:4:8: error: use of deleted function 'main()::<lambda()>& main()::<lambda()>::operator=(const main()::<lambda()>&)' 4 | f2 = f1; | ^~ a3.cpp:2:14: note: a lambda closure type has a deleted copy assignment operator 2 | auto f1 = []{}; | ^ |
希望通过本文,对理解Lambda有所帮助。
8. 参考资料
1) https://zh.cppreference.com/w/cpp/language/lambda
2) https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=vs-2019
3) https://en.cppreference.com/w/cpp/language/lambda
4) https://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11
5) https://www.cprogramming.com/c++11/c++11-lambda-closures.html
C++之Lambda研究的更多相关文章
- 搭建一套自己实用的.net架构(3)续 【ORM Dapper+DapperExtensions+Lambda】
前言 继之前发的帖子[ORM-Dapper+DapperExtensions],对Dapper的扩展代码也进行了改进,同时加入Dapper 对Lambda表达式的支持. 由于之前缺乏对Lambda的知 ...
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...
- Linq表达式和Lambda表达式用法对比
什么是Linq表达式?什么是Lambda表达式?前一段时间用到这个只是,在网上也没找到比较简单明了的方法,今天就整理了一下相关知识,有空了再仔细研究研究 public Program() { List ...
- 委托,匿名函数和lambda表达式
很早之前就接触到了委托,但是一直对他用的不是太多,主要是本人是菜鸟,能写的比较高级的代码确实不多,但是最近在看MSDN微软的类库的时候,发现了微软的类库好多都用到了委托,于是决定好好的研究研究,加深一 ...
- 【转贴】Python处理海量数据的实战研究
最近看了July的一些关于Java处理海量数据的问题研究,深有感触,链接:http://blog.csdn.net/v_july_v/article/details/6685962 感谢July ^_ ...
- C# 从CIL代码了解委托,匿名方法,Lambda 表达式和闭包本质
前言 C# 3.0 引入了 Lambda 表达式,程序员们很快就开始习惯并爱上这种简洁并极具表达力的函数式编程特性. 本着知其然,还要知其所以然的学习态度,笔者不禁想到了几个问题. (1)匿名函数(匿 ...
- [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...
- 深入探索Java 8 Lambda表达式
2014年3月,Java 8发布,Lambda表达式作为一项重要的特性随之而来.或许现在你已经在使用Lambda表达式来书写简洁灵活的代码.比如,你可以使用Lambda表达式和新增的流相关的API,完 ...
- C#在泛型类中,通过表达式树构造lambda表达式
场景 最近对爬虫的数据库架构做调整,需要将数据迁移到MongoDB上去,需要重新实现一个针对MongoDB的Dao泛型类,好吧,动手开工,当实现删除操作的时候问题来了. 我们的删除操作定义如下:voi ...
随机推荐
- [UOJ #393]【NOI2018】归程
题目大意:有一张$n$个点$m$条边的图,每个边有两个属性$a_i,b_i$.有$Q$个询问,每个询问给出$v,p$,表示所有边中$b_i\leqslant p$的边会被标记,在点$v$,可以通过不被 ...
- 小米9安装charles证书
一.打开你 mac 中对应的 charles 二.点击右上角的help按钮,打开帮助弹窗 三.点击帮助弹窗中的SSL Proxying,选择save charles root certificatio ...
- sleep方法动态打印 C语言
#include<Windows.h> #include<stdio.h> #include<stdlib.h> #include<string.h> ...
- int转换为String,常用的四种方法。
int i = 100; 方法一:String s1 = i + " "; 方法二:String s2 = String.valueof(i); 方法三(先转换为Integer类型 ...
- Java自学-数组 排序
Java 数组选择法,冒泡法排序 步骤 1 : 选择法排序 选择法排序的思路: 把第一位和其他所有的进行比较,只要比第一位小的,就换到第一个位置来 比较完后,第一位就是最小的 然后再从第二位和剩余的其 ...
- 【转载】C#中Convert.ToInt32方法将字符串转换为Int32类型
在C#编程过程中,可以使用Convert.ToInt32方法将字符串或者其他可转换为数字的对象变量转换为ToInt32类型,Convert.ToInt32方法有多个重载方法,最常使用的一个方法将字符串 ...
- python多任务的实现:线程,进程,协程
什么叫“多任务”呢?简单地说,就是操作系统可以同时运行多个任务.打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行.还有很多任务悄悄地在后 ...
- C# winform 托盘控件的使用
从工具栏里,把NotifyIcon控件拖到窗体上,并设置属性: 1.visible 设置默认为FALSE: 2.Image 选一张图片为托盘时显示的图样:比如选奥巴马卡通画像: 3.Text 显示: ...
- ElasticSearch(十一):Spring Data ElasticSearch 的使用(一)
1.环境准备 我本地使用的环境为: 虚拟机版本:Centos 7.3 两台 IP 分别为:192.168.56.12, 192.168.56.13 Elasticsearch版本:6.4.0 ( ...
- 【HICP Gauss】数据库 数据库管理(shutdown 日志 连接命令)-5
数据库关闭终止Zengine进程关闭数据库 会导致无法预料的状态 建议shutdown关闭数据库 shutdown 模式1.normal默认 停止新请求 断开等待会话 关闭服务 终止主进程 需要连接发 ...