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背后一面,以方便更好的理解和掌握LambdaLambda代碼段實際為一個編譯器生成的類的“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++ RTTIRun-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++11C++14C++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

 

 


免責聲明!

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



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