前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++之Lambda研究

C++之Lambda研究

作者头像
一见
发布2019-06-04 14:20:45
7550
发布2019-06-04 14:20:45
举报
文章被收录于专栏:蓝天蓝天

1. 前言

本文代码测试环境为“GCC-9.1.0”,有关编译器的安装请参考《安装GCC-8.3.0及其依赖》,适用于“GCC-9.1.0”。

本文试图揭露Lambda背后一面,以方便更好的理解和掌握Lambda。Lambda代码段实际为一个编译器生成的类的“operator ()”函数,编译器会为每一个Lambda函数生成一个匿名的类(在C++中,类和结构体实际一样,无本质区别,除了默认的访问控制)。

对Lambda的最简单理解,是将它看作一个匿名类(或结构体),实际上也确实如此,编译器把Lambda编译成了匿名类。

2. 示例1

先看一段几乎最简单的Lambda代码:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
int main() {
auto f = [] { printf("f\n"); }; // 注意“}”后的“;”必不可少,否则编译报错
return 0;
}

如果Lambda表达式(或函数)没有以“;”结尾,则编译时将报如下错误:

代码语言:javascript
复制
a3.cpp: In function 'int main()':
a3.cpp:4:3: error: expected ',' or ';' before 'return'
4 |   return 0;
|   ^~~~~~

Lambda之所以神奇,这得益于C++编译器的工作,上述“f”实际长这样:

代码语言:javascript
复制
type = struct  {
}

一个匿名的类(或结构体),实际上还有一个成员函数“operator () const”。注意这里成员函数是”const”类型,这是默认的。如果需非”const”成员函数,需要加”mutable”修饰,如下所示:

代码语言:javascript
复制
auto f = [n]() mutable { printf("%d\n", n); };

上面例子对应的匿名类没有任何类数据成员,现在来个有类数据成员的代码:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
int main() {
int n = 3;
auto f = [n] { printf("%d\n", n); };
f(); // 这里实际调用的是匿名类的成员函数“operator ()”
return 0;
}

这时,“f”实际长这样,它是一个含有类数据成员的匿名类,而不再是空无一特的类:

代码语言:javascript
复制
type = struct  {
int __n;
}

3. 示例2

继续来个变种:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
int main() {
int n = 3;
auto f = [&n]() mutable { printf("%d\n", n); };
f();
return 0;
}

这时,“f”实际长这样,一个包含了引用类型的匿名类:

代码语言:javascript
复制
type = struct  {
int &__n;
}

4. 示例3

继续变种,“&”的作用让Lambda函数可使用Lambda所在作用域内所有可见的局部变量(包括Lambda所在类的this),并且是以引用传递方式:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
int main() {
int n = 3;
auto f = [&]() mutable { printf("%d\n", n); };
f();
return 0;
}

“f”实际长这样:

代码语言:javascript
复制
type = struct  {
int &__n;
}

变稍复杂一点:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
int main() {
int n = 3;
int m = 5;
auto f = [&]() mutable { printf("%d\n", n); };
f();
return 0;
}

可以看到,“f”并没有发生变化:

代码语言:javascript
复制
type = struct  {
int &__n;
}

5. 示例4

继续增加复杂度:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
int main() {
int n = 3;
int m = 5;
auto f = [&]() mutable { printf("%d,%d\n", n, m); };
f();
return 0;
}

可以看到“f”变了:

代码语言:javascript
复制
type = struct  {
int &__n;
int &__m;
}

从上面不难看出,编译器只会把Lambda函数用到的变量打包进对应的匿名类。继续一个稍复杂点的:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
struct X {
void foo() { printf("foo\n"); }
void xoo() {
auto f = [&] { foo(); };
f();
}
};
int main() {
X().xoo();
return 0;
}

这时,“f”实际长这样:

代码语言:javascript
复制
type = struct X:: {
X * const __this; // X类型的指针(非对象)
}

如果将“auto f = [&] { foo(); };”中的“&”去掉,则会遇到编译错误,提示“this”没有被Lambda函数捕获:

代码语言:javascript
复制
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

改成下列方式捕获也是可以的:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
struct X {
void foo() { printf("foo\n"); }
void xoo() {
auto f = [this] { foo(); };
f();
}
};
int main() {
X().xoo();
return 0;
}

如果是C++17,还可以这样:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++17
#include
struct X {
void foo() { printf("foo\n"); }
void xoo() {
auto f = [*this]() mutable { foo(); };
f();
}
};
int main() {
X().xoo();
return 0;
}

注意得有“mutable”修饰,不然报如下编译错误:

代码语言:javascript
复制
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"); }
|        ^~~

也可以这样:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++17
#include
struct X {
void foo() { printf("foo\n"); }
void xoo() {
auto f = [&,*this]() mutable { foo(); };
f();
}
};
int main() {
X().xoo();
return 0;
}

使用“*this”时的“f”样子如下:

代码语言:javascript
复制
type = struct X:: {
X __this; // X类型的对象(非指针)
}

6. 示例5

继续研究,使用C++ RTTI(Run-Time Type Identification,运行时类型识别)设施“typeid”查看Lambda函数:

代码语言:javascript
复制
// g++ -g -o a1 a1.cpp -std=c++11
#include
#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;
}

运行输出:

代码语言:javascript
复制
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;”将触发编译错误:

代码语言:javascript
复制
int main() {
auto f1 = []{};
auto f2 = f1;
f2 = f1;
return 0;
}

上列代码在C++11、C++14和C++17均会报错。不过如规则所示,C++20(含C++2a)上则可以正常编译:

代码语言:javascript
复制
a3.cpp: In function 'int main()':
a3.cpp:4:8: error: use of deleted function 'main()::& main()::::operator=(const main()::&)'
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-05-30 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 前言
  • 2. 示例1
  • 3. 示例2
  • 4. 示例3
  • 5. 示例4
  • 6. 示例5
  • 7. 匿名类规则
  • 8. 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档