目录

目录 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研究的更多相关文章

  1. 搭建一套自己实用的.net架构(3)续 【ORM Dapper+DapperExtensions+Lambda】

    前言 继之前发的帖子[ORM-Dapper+DapperExtensions],对Dapper的扩展代码也进行了改进,同时加入Dapper 对Lambda表达式的支持. 由于之前缺乏对Lambda的知 ...

  2. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  3. Linq表达式和Lambda表达式用法对比

    什么是Linq表达式?什么是Lambda表达式?前一段时间用到这个只是,在网上也没找到比较简单明了的方法,今天就整理了一下相关知识,有空了再仔细研究研究 public Program() { List ...

  4. 委托,匿名函数和lambda表达式

    很早之前就接触到了委托,但是一直对他用的不是太多,主要是本人是菜鸟,能写的比较高级的代码确实不多,但是最近在看MSDN微软的类库的时候,发现了微软的类库好多都用到了委托,于是决定好好的研究研究,加深一 ...

  5. 【转贴】Python处理海量数据的实战研究

    最近看了July的一些关于Java处理海量数据的问题研究,深有感触,链接:http://blog.csdn.net/v_july_v/article/details/6685962 感谢July ^_ ...

  6. C# 从CIL代码了解委托,匿名方法,Lambda 表达式和闭包本质

    前言 C# 3.0 引入了 Lambda 表达式,程序员们很快就开始习惯并爱上这种简洁并极具表达力的函数式编程特性. 本着知其然,还要知其所以然的学习态度,笔者不禁想到了几个问题. (1)匿名函数(匿 ...

  7. [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

  8. 深入探索Java 8 Lambda表达式

    2014年3月,Java 8发布,Lambda表达式作为一项重要的特性随之而来.或许现在你已经在使用Lambda表达式来书写简洁灵活的代码.比如,你可以使用Lambda表达式和新增的流相关的API,完 ...

  9. C#在泛型类中,通过表达式树构造lambda表达式

    场景 最近对爬虫的数据库架构做调整,需要将数据迁移到MongoDB上去,需要重新实现一个针对MongoDB的Dao泛型类,好吧,动手开工,当实现删除操作的时候问题来了. 我们的删除操作定义如下:voi ...

随机推荐

  1. HDU校赛 | 2019 Multi-University Training Contest 1

    2019 Multi-University Training Contest 1 http://acm.hdu.edu.cn/contests/contest_show.php?cid=848 100 ...

  2. CF1063F String Journey DP、SAM、线段树

    传送门 为了方便把串反过来,条件变为\(t_i\)是\(t_{i+1}\)的真子串,答案显然不变. 一件重要的事情是必定存在一种最优解,字符串序列\(\{t\}\)满足\(|t_i| = i\). 考 ...

  3. java枚举enum总结大全

    1.注意点 (1)枚举中的构造方法必须是private的. (2)枚举中可以定义抽象方法和一般方法,但枚举对象必须实现所有抽象方法. (3)枚举对象必须放在第一行. package classTwo0 ...

  4. 【解决方案】ArcGIS导入要素集后没反应

    内容源自:ArcGIS10.2基础教程(丁华) 书上要求: 1.在“练习”文件夹中新建一个名为“沈阳”的个人地理数据库和名为“shenyang”的要素集,设置地理坐标为“Xi'an 1980”,高程坐 ...

  5. 使用 Create-React-App 开发 Chrome 扩展

    整理 Kindle 标注.书签和笔记从未如此简单! Kindle 标注管理应用 Kindle Mate 只支持 Windows,不支持 Mac.标注只是解析我的剪贴文本文件,配合 FileReader ...

  6. Windows上安装ElasticSearch7

    安装JDK1.8(包括)以上版本 安装ElasticSearch ElasticSearch下载地址: https://www.elastic.co/downloads/elasticsearch 双 ...

  7. JAVA 容器配置 JVM 监控

    目前世面上较流行的JAVA容器工具有:tomcat,jboss,weblogic 在日常工作中,经常会遇到开发需要查看JVM相关信息,这时就需要开启JVM. 一.tomcat 1 修改jdk认证配置文 ...

  8. golang中逗号ok模式_转

    ,ok,第一个参数是一个值或者nil,第二个参数是true/false或者一个错误error.在一个需要赋值的if条件语句中,使用这种模式去检测第二个参数值会让代码显得优雅简洁.这种模式在go语言编码 ...

  9. python函数调用时参数传递方式

    python函数调用时参数传递方式 C/C++参数传递方式 对于C程序员来说,我们都知道C在函数调用时,采用的是值传递,即形参和实参分配不同的内存地址,在调用时将实参的值传给实参,在这种情况下,在函数 ...

  10. 8.7 —— 排序函数及 splice 插入

    .排序,按自己的逻辑 nid_item_vec.sort([](const NID_PBDATA &l, const NID_PBDATA &r) -> bool { retur ...