• 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 "); }; // 注意“}”后的“;”必不可少,否则编译报错

      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); };

     

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

    // g++ -g -o a1 a1.cpp -std=c++11

    #include <stdio.h>

    int main() {

      int n = 3;

      auto f = [n] { printf("%d ", 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); };

      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); };

      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); };

      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, 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 "); }

      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 "); }

      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 "); }

      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 "); }

          |        ^~~

     

    也可以这样:

    // g++ -g -o a1 a1.cpp -std=c++17

    #include <stdio.h>

    struct X {

      void foo() { printf("foo "); }

      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 "); };

        printf("%s ", 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

     

     

  • 相关阅读:
    《剑指offer》— JavaScript(1)二维数组中的查找
    《JavaScript高级程序设计(第三版)》-3
    HTML+CSS基础
    HTML5新增属性
    《javascript高级程序设计(第3版)》-1
    wp8.1 Study7: ListView 和GridView应用
    wp8.1 Study6: App的生命周期管理
    WP8.1 Study5:Data binding数据绑定
    WP8.1 Study4:WP8.1中控件集合应用
    WP8.1 Study3:WP8.1中Animation应用
  • 原文地址:https://www.cnblogs.com/aquester/p/10947765.html
Copyright © 2020-2023  润新知