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 ...
随机推荐
- pytest_函数传参和firture传参数request
前言为了提高代码的复用性,我们在写用例的时候,会用到函数,然后不同的用例去调用这个函数. 比如登录操作,大部分的用例都会先登录,那就需要把登录单独抽出来写个函数,其它用例全部的调用这个登陆函数就行. ...
- Linux RedHat 7 配置本地 YUM源
尽管RPM安装方法能够帮助用户查询软件相关的依赖关系,但是还是需要安装人员自己来解决,而且有些大型软件可能与数十个程序都有依赖关系,在这种情况下安装软件事件非常痛苦和耗费事件的事情,而Yum软件仓库可 ...
- ASP.NET WebApi 学习与实践系列(2)---WebApi 路由请求的理解
目录 写在前面 WebApi Get 请求 1.无参数的请求 2.一个参数的请求 3.多个参数的请求 4.实体参数的请求 WebApi Post 请求 1.键值对请求 2.dynamic 动态类型请求 ...
- [JLOI2014]松鼠的新家 (树剖)
题目 P3258 [JLOI2014]松鼠的新家 解析 非常裸的一道树剖题 链上修改+单点查询的板子 记录一下所经过的点\(now[i]\),每次更新\(now[i-1]到now[i]\) 我们链上更 ...
- Java之路---Day04
2019-10-18-21:35:36 面向对象 学面向对象前要先知道什么叫面向过程,了解了什么叫面向过程才容易理解面向对象 面向过程(强调步骤) 概念:当需要实现一个功能的时候,每一个具体的步骤都要 ...
- mybatis 变更xml文件目录
mybatis的xml默认读取的是resources目录,这个目录是可以变化的.我习惯于将mapper文件和xml放到一起或相邻目录下. 如图: 具体操作: 以mybatis-plus为例 boots ...
- C++字符串相互转换
转自cs_wu原文 C++ char*,const char*,string的相互转换 1. string转const char* string s ="abc"; const c ...
- EF 拉姆达 linq if else (整理)
首先想到: var data0 = db.T_Plants2; //这里加.AsQueryable() ) { .Where(d => d.NaturalEcosystem == true); ...
- Linux 里的 2>&1 究竟是什么
原文 我们在Linux下经常会碰到nohup command>/dev/null 2>&1 &这样形式的命令.首先我们把这条命令大概分解下: 首先就是一个nohup:表示当 ...
- 搭建helm私服ChartMuseum
介绍 ChartMuseum是一个用Go(Golang)编写的开源Helm Chart Repository服务器 ChartMuseum是一个用Go(Golang)编写的开源Helm Chart R ...