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 ...
随机推荐
- springboot使用HttpSessionListener 监听器统计当前在线人数
概括: request.getSession(true):若存在会话则返回该会话,否则新建一个会话. request.getSession(false):若存在会话则返回该会话,否则返回NULL ht ...
- java之spring之对象的创建
首先提供目录结构如下: 下面提供各文件代码,以供参考: UserDynamicFactory.java package cn.sxt.factory; import cn.sxt.vo.User; / ...
- 2019 昆仑万维java面试笔试题 (含面试题解析)
本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.昆仑万维等公司offer,岗位是Java后端开发,因为发展原因最终选择去了昆仑万维,入职一年时间了,也成为了面 ...
- 从客户端中检测到有潜在危险的 Request.QueryString 值
解决办法: 一.解决方法是在web.config的 里面加入<system.web> <pages validateRequest="false"/>< ...
- 手写DAO框架(七)-如何保证连接可用
版权声明:本文为博客园博主「水木桶」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明.原文链接:https://www.cnblogs.com/shuimutong/p ...
- 设计的一些kubernetes面试题
公司现在上了一部分的业务至k8s,老实说,我心里很慌,在项目改造中,每天都会遇到很多问题,好友找我出一份k8s面试题,参考了网上的一些,再加上自己公司遇到的一些问题,整理如下: 参考链接:http:/ ...
- UAVCAN DSDL介绍
原文:http://uavcan.org/Specification/3._Data_structure_description_language/ DSDL:Data structure descr ...
- linux搭建stm32开发环境
下载stm32固件库 创建目录 libs目录放stm32固件库,src放用户源码,inc放用户头文件 # mkdir libs src inc 复制文件 将STM32F10x_StdPeriph_Li ...
- MySQL MGR--MGR部署
MGR部署 场景描述: 使用三台服务器搭建一个简单MGR集群,使用MySQL 5.7.24版本,服务器列表为: 192.168.1.147 192.168.1.148 192.168.1.149 1. ...
- H3C 802.11 MAC层工作原理