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 ...
随机推荐
- CSS控制DIV水平垂直居中
<div style="position:absolute; width: 600px; height: 200px; left: 50%; top: 50%; margin-left ...
- 最全的 pip 使用指南,50% 你可能没用过
所有的 Python 开发者都清楚,Python 之所以如此受欢迎,能够在众多高级语言中,脱颖而出,除了语法简单,上手容易之外,更多还要归功于 Python 生态的完备,有数以万计的 Python 爱 ...
- 特征选择之FeatureSelector工具
项目地址:https://github.com/WillKoehrsen/feature-selector 特征选择(feature selection)是查找和选择数据集中最有用特征的过程,是机器学 ...
- JAVA基础之JavaEE与MVC
所谓的架构.模式都是方便开发和查看的,分工明确的,理解每层的具体的意义! 一.JavaEE: 1.Java EE,Java 平台企业版(Java Platform Enterprise Edition ...
- 调用Hybris API时遇到的错误消息Cannot find user with uid如何解决
今天工作中试图调用Commerce Cloud的user creation API用代码创建Hybris用户时,遇到下面这个错误消息. 我觉得很奇怪,因为backoffice里能查到这个id为jerr ...
- tmp-scmj
1.apk下载 http://app6.wpf2x4.cn/?downloadfrom=sichuan 麻将图: Big_mahjonghn_tiles_sr.png
- python系列:二、Urllib库的高级用法
1.设置Headers 有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些Headers 的属性. 打开我们的浏览器,调 ...
- golang之网络开发
TCP Server/Client开发 net包提供network I/O开发接口,包括TCP/IP.UDP.DNS和Unix domain sockets. 常用开发一般仅需要最基础接口或函数: 服 ...
- 嵌入式开发之移植OpenCv可执行程序到arm平台
0. 序言 PC操作系统:Ubuntu 16.04 OpenCv版本:4.0 交叉工具链:arm-linux-gnueabihf,gcc version 5.4.0 目标平台:arm 编译时间:201 ...
- HTML&CSS基础-html的图片标签
HTML&CSS基础-html的图片标签 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.如下图所示,准备一张图片,存放路径和html文件在同一目录 二.HTML源代码 ...