zoukankan      html  css  js  c++  java
  • C++ Lambda 编译器实现原理

    Lambda 表达式语法

    Lambda 表达式完整的格式如下:

    [捕获列表] (形参列表) mutable 异常列表-> 返回类型
    {
        函数体
    }
    

    各项的含义:

    1. 捕获列表:捕获外部变量,捕获的变量可以在函数体中使用,可以省略,即不捕获外部变量。
    2. 形参列表:和普通函数的形参列表一样。可省略,即无参数列表
    3. mutable:mutable 关键字,如果有,则表示在函数体中可以修改捕获变量,根据具体需求决定是否需要省略。
    4. 异常列表:noexcept / throw(...),和普通函数的异常列表一样,可省略,即代表可能抛出任何类型的异常。
    5. 返回类型:和函数的返回类型一样。可省略,如省略,编译器将自动推导返回类型。
    6. 函数体:代码实现。可省略,但是没意义。

    使用示例

    void LambdaDemo()
    {
        int a = 1;
        int b = 2;
        auto lambda = [a, b](int x, int y)mutable throw() -> bool
        {
            return a + b > x + y;
        };
        bool ret = lambda(3, 4);
    }
    
    

    编译器实现原理

    编译器实现 lambda 表达式大致分为一下几个步骤

    1. 创建 lambda 类,实现构造函数,使用 lambda 表达式的函数体重载 operator()(所以 lambda 表达式 也叫匿名函数对象)
    2. 创建 lambda 对象
    3. 通过对象调用 operator()

    编译器将 lambda 表达式翻译后的代码:

    class lambda_xxxx
    {
    private:
        int a;
        int b;
    public:
        lambda_xxxx(int _a, int _b) :a(_a), b(_b)
        {
        }
        bool operator()(int x, int y) throw()
        {
            return a + b > x + y;
        }
    };
    void LambdaDemo()
    {
        int a = 1;
        int b = 2;
        lambda_xxxx lambda = lambda_xxxx(a, b);
        bool ret = lambda.operator()(3, 4);
    }
    

    其中,类名 lambda_xxxx 的 xxxx 是为了防止命名冲突加上的。

    lambda_xxxx 与 lambda 表达式 的对应关系

    1. lambda 表达式中的捕获列表,对应 lambda_xxxx 类的 private 成员
    2. lambda 表达式中的形参列表,对应 lambda_xxxx 类成员函数 operator() 的形参列表
    3. lambda 表达式中的 mutable,对应 lambda_xxxx 类成员函数 operator() 的常属性 const,即是否是 常成员函数
    4. lambda 表达式中的返回类型,对应 lambda_xxxx 类成员函数 operator() 的返回类型
    5. lambda 表达式中的函数体,对应 lambda_xxxx 类成员函数 operator() 的函数体

    另外,lambda 表达 捕获列表的捕获方式,也影响 对应 lambda_xxxx 类的 private 成员 的类型

    1. 值捕获:private 成员 的类型与捕获变量的类型一致
    2. 引用捕获:private 成员 的类型是捕获变量的引用类型

    不捕获任何外部变量

    如果 lambda 表达式不捕获任何外部变量,在特定的情况下,会有额外的代码生成。
    其中,特定情况是指:有 lambda_xxxx 类函数指针 的类型转换
    如以下代码

    typedef int(_stdcall *Func)(int);
    int Test(Func func)
    {
    	return func(1);
    }
    void LambdaDemo()
    {
    	Test([](int i) {
    		return i;
    	});
    }
    

    Test 函数接受一个函数指针作为参数,并调用这个函数指针。

    实际调用 Test 时,传入的参数却是一个 Lambda 表达式,所以这里有一个类型的隐式转换
    lambda_xxxx => 函数指针。

    上面已经提到,Lambda 表达式就是一个 lambda_xxxx 类的匿名对象,与函数指针之间按理说不应该存在转换,但是上述代码却没有问题。

    其问题关键在于,上述代码中,lambda 表达式没有捕获任何外部变量,即 lambda_xxxx 类没有任何成员变量,在 operator() 中也就不会用到任何成员变量,也就是说,operator() 虽然是个成员函数,它却不依赖 this 就可以调用。

    因为不依赖 this,所以 一个 lambda_xxxx 类的匿名对象与函数指针之间就存在转换的可能。

    大致过程如下:

    1. 在 lambda_xxxx 类中生成一个静态函数,静态函数的函数签名与 operator() 一致,在这个静态函数中,通过一个空指针去调用该类的 operator()
      2.在 lambda_xxxx 重载与函数指针的类型转换操作符,在这个函数中,返回第 1 步中静态函数的地址。

    上述代码在编译器的翻译后代码如下:

    typedef int(_stdcall *Func)(int);
    
    class lambda_xxxx 
    {
    private:
    	//没有捕获任何外部变量,所有没有成员
    public:
            /*...省略其他代码...*/
    	int operator()(int i)
    	{
    		return i;
    	}
    	static int _stdcall lambda_invoker_stdcall(int i)
    	{
    		return ((lambda_xxxx *)nullptr)->operator()(i);
    	}
    
    	operator Func() const
    	{
    		return &lambda_invoker_stdcall;
    	}
    };
    
    int Test(Func func)
    {
    	return func(1);
    }
    void LambdaDemo()
    {
    	auto lambda = lambda_xxxx ();
    	Func func = lambda.operator Func();
    	Test(func);
    }
    

    上述代码只是以 __stdcall 调用约定的函数指针举例,实际使用时,对于不同调用约定,会生成对应版本的静态函数和类型转换函数

    以上结论的正确性可以通过显式调用 lambda 的转换函数与反汇编来证明

    void LambdaDemo()
    {
    	auto lambda = [](int i) {return i;};
    	Func func = lambda.operator Func();
    	Test(func);
    }
    
    • 转为函数指针

    1png

    738 x 691221 x 114

    • 将静态函数 lambda_invoker_stdcall 地址作为 类型转换函数的返回值

    2png

    • 静态函数 lambda_invoker_stdcall 中,使用 0 作为 this 调用 operator()

    3png

  • 相关阅读:
    JSP(一)
    设计模式之UML类图
    Servle原理
    Servlet 浅谈(三)
    Servlet 浅谈(二)
    Servlet 浅谈(一)
    闲聊
    设计模式之装饰器模式
    struts2源码调试环境的搭建
    Github学习
  • 原文地址:https://www.cnblogs.com/cute/p/12455769.html
Copyright © 2011-2022 走看看