目錄 1html
1. 前言 1c++
2. 示例1 1express
3. 示例2 2函數
4. 示例3 3測試
5. 示例4 3this
6. 示例5 6spa
7. 匿名類規則 6.net
8. 參考資料 7指針
本文代碼測試環境爲「GCC-9.1.0」,有關編譯器的安裝請參考《安裝GCC-8.3.0及其依賴》,適用於「GCC-9.1.0」。c++11
本文試圖揭露Lambda背後一面,以方便更好的理解和掌握Lambda。Lambda代碼段實際爲一個編譯器生成的類的「operator ()」函數,編譯器會爲每個Lambda函數生成一個匿名的類(在C++中,類和結構體實際同樣,無本質區別,除了默認的訪問控制)。
對Lambda的最簡單理解,是將它看做一個匿名類(或結構體),實際上也確實如此,編譯器把Lambda編譯成了匿名類。
先看一段幾乎最簡單的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; } |
繼續來個變種:
// 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; } |
繼續變種,「&」的做用讓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; } |
繼續增長複雜度:
// 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類型的對象(非指針) } |
繼續研究,使用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_ |
編譯器爲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有所幫助。
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