C++中lambda的應用場景及編譯器實現原理


一、使用場景

1、局部函數的需求場景及限制

在標准C++(C++11之前)中,是沒有局部函數這種語法的。但是在有些場景下,使用局部函數可以極大的簡化代碼,比方說,輸入參數是一個圓(由圓心和半徑表示),判斷給定的兩個點,是否一個在圓外部,一個在內部,此時最好有一個判斷一個點是否在圓形內部的函數接口,然后分別判斷兩個點。
struct Point
{
int x;
int y;
};
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
{
bool PointInCircle(Point &rstPoint)
{
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
}
return PointInCircle(P1) != PointInCircle(P2);
}

但是,由於C++不允許使用局部函數,所以上面的代碼並不能編譯通過。在lambda函數引入之前,通常的變通方法是定義局部類型,並使用靜態函數來作為局部函數,但是限制是不能使用所在函數的局部變量。
tsecer@harry: cat -n localstruct.cpp
1 struct Point
2 {
3 int x;
4 int y;
5 };
6 bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
7 {
8 struct localstruct
9 {
10 static bool PointInCircle(Point &rstPoint)
11 {
12 return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
13 }
14 };
15 return localstruct::PointInCircle(P1) != localstruct::PointInCircle(P2);
16 }
tsecer@harry: g++ -c localstruct.cpp
localstruct.cpp: 在靜態成員函數‘static bool IsTrue(Point&, int, Point&, Point&)::localstruct::PointInCircle(Point&)’中:
localstruct.cpp:12:25: 錯誤:use of parameter from containing function
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
^
localstruct.cpp:6:6: 錯誤:‘Point& rstCenter’已在此聲明過
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
^
localstruct.cpp:12:54: 錯誤:use of parameter from containing function
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
^
localstruct.cpp:6:6: 錯誤:‘Point& rstCenter’已在此聲明過
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
^
localstruct.cpp:12:83: 錯誤:use of parameter from containing function
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
^
localstruct.cpp:6:6: 錯誤:‘Point& rstCenter’已在此聲明過
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
^
localstruct.cpp:12:112: 錯誤:use of parameter from containing function
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
^
localstruct.cpp:6:6: 錯誤:‘Point& rstCenter’已在此聲明過
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
^
localstruct.cpp:12:128: 錯誤:use of parameter from containing function
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
^
localstruct.cpp:6:6: 錯誤:‘int iRadius’已在此聲明過
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
^
localstruct.cpp:12:138: 錯誤:use of parameter from containing function
return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
^
localstruct.cpp:6:6: 錯誤:‘int iRadius’已在此聲明過
bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
^
tsecer@harry:

 

2、lambda函數的解決方法

tsecer@harry: cat -n locallambda.cpp
1 struct Point
2 {
3 int x;
4 int y;
5 };
6
7 bool IsTrue(Point &rstCenter, int iRadius, Point &P1, Point &P2)
8 {
9 auto PointInCircle = [&](Point &rstPoint) -> bool
10 {
11 return (rstPoint.x - rstCenter.x) * (rstPoint.x - rstCenter.x) + (rstPoint.y - rstCenter.y) * (rstPoint.y - rstCenter.y) <= iRadius * iRadius;
12 };
13 return PointInCircle(P1) != PointInCircle(P2);
14 }
tsecer@harry: g++ -c -std=c++11 locallambda.cpp
tsecer@harry:
tsecer@harry:

二、實現原理

大致來說,gcc對於lambda函數的實現,相當於是允許通過捕捉語句聲明引用哪些所在函數的局部變量,把這些捕捉變量放在一個結構中,並在結構中定義一個函數操作符。這些看起來和自己實現比較類似,但是區別在於它添加了語法,允許明確聲明的局部變量引用,而且會幫助lambda函數自動計算引用了哪些所在函數的局部變量,編譯時生成對應的類型和操作符。
tsecer@harry: cat -n autofunc.cpp
1 #include <functional>
2
3 typedef std::function<bool (int, int)> stdFunc;
4
5 int main(int argc, const char * argv[])
6 {
7 int a, b, c, d;
8 auto ff = [&](int x, int y) -> bool
9 {
10 return x + y + a + d;
11 };
12
13 ff(0x1111, 0x2222);
14
15 stdFunc stdff = ff;
16 stdff(0x3333, 0x4444);
17 }
tsecer@harry: g++ -g -std=c++11 autofunc.cpp
tsecer@harry: gdb ./a.out
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/harry/study/autofunc/a.out...done.
(gdb) b main
Breakpoint 1 at 0x400874: file autofunc.cpp, line 11.
(gdb) r
Starting program: /home/harry/study/autofunc/./a.out

Breakpoint 1, main (argc=1, argv=0x7fffffffe578) at autofunc.cpp:11
11 };
Missing separate debuginfos, use: debuginfo-install glibc-2.17-196.tl2.3.x86_64 libgcc-4.8.5-4.el7.x86_64 libstdc++-4.8.5-4.el7.x86_64
(gdb) ptype ff
type = struct __lambda0 {
int &__a;
int &__d;
}
(gdb) s
13 ff(0x1111, 0x2222);
(gdb)
__lambda0::operator() (__closure=0x7fffffffe460, x=4369, y=8738) at autofunc.cpp:10
10 return x + y + a + d;
(gdb) ptype __closure
type = const struct __lambda0 {
int &__a;
int &__d;
} * const
(gdb)

可以看到,由於lambda函數ff引用了局部變量a、b,編譯器在編譯時動態生成了這樣一個結構
struct __lambda0 {
int &__a;
int &__d;
bool operator()(int x, int y)
{
return x + y+ __a + __b;
}
}
反匯編中的__closure看作結構的this指針也可以。

三、和std庫中functional的交互

知道它的實現原理之后,它就可以和std::function一起使用了,並且和定義了函數調用操作符的結構使用方法相同:
tsecer@harry: cat -n lambdabind.cpp
1 #include <functional>
2
3 typedef std::function<int (int, int)> stdFunc;
4
5 int main(int argc, const char * argv[])
6 {
7 auto ff = [&](int x) -> int
8 {
9 return x + argc;
10 };
11 stdFunc stdff = std::bind(ff, std::placeholders::_1);
12 return stdff(11, 22);
13 }
14
tsecer@harry: g++ -g -std=c++11 lambdabind.cpp
tsecer@harry: ./a.out
tsecer@harry: echo $?
12


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM