lambda原理篇

举报
dosomething 发表于 2023/12/01 17:45:54 2023/12/01
【摘要】 demoint main(){ int inc = 10; auto lambda = [&inc](int i) -> int{ return i + inc; }; std::cout << lambda(1) << std::endl;}使用规则语法:[捕获参数](参数) mutable -> 返回 { 函数体};1.1 捕获参数捕获参数:捕...

demo

int main()
{
    int inc = 10;
    auto lambda = [&inc](int i) -> int{
        return i + inc;
    };
    std::cout << lambda(1) << std::endl;
}

使用规则

语法:

[捕获参数](参数) mutable -> 返回 {
    函数体
};

1.1 捕获参数

捕获参数:捕获的外部变量,用于函数体内访问外部变量;不指定,则不捕获任何外部变量

捕获方式:

&变量名 以引用方式捕获
变量名 已拷贝方式捕获
& 以引用方式捕获当前环境下的所有外部变量
= 以拷贝方式捕获当前环境下的所有外部变量

注意点:

1、类中this的捕获方式,[this]、[=]、[&]均是以引用方式捕获this;

2、如果需要以拷贝方式捕获this,[*this]

3、以引用方式捕获数据时,需确保捕获的数据在lambda使用时未被释放,特别是表达式跨函数栈传输时

1.2 mutable

        mutable用于确认捕获的参数时候可以在lambda内部修改

有捕获原理分析

拿demo来进行分析,还原demo编译后的内容

int main()
{
    int inc = 10;

    class __lambda_7_19
    {
    public:
        inline /*constexpr */ int operator()(int i) const
        {
            return i + inc;
        }

    private:
        int &inc;

    public:
        __lambda_7_19(int & _inc)
            : inc{ _inc }
        {}

    };

    __lambda_7_19 lambda = __lambda_7_19{ inc };
    std::cout.operator<<(lambda.operator()(1)).operator<<(std::endl);
    return 0;
}

对上述代码进行格式化后:

int main()
{
    class __lambda_7_19
    {
    public:
        // 重载运算符(),const代表无法修改成员变量inc
        inline int operator()(int i) const
        {
            return i + inc;
        }
        // 构造函数
        __lambda_7_19(int & _inc)
            : inc{ _inc }
        {}

    private:
        // 以引用的方式和外部变量inc共用同一块内存
        int & inc;
    };

    int inc = 10;
    // 类对象初始化
    __lambda_7_19 lambda = __lambda_7_19{ inc };
    
    std::cout.operator<<(lambda.operator()(1)).operator<<(std::endl);
    return 0;
}

从上述内容可以看出一下几点:

1、lambda表达式会转换为类对象

2、捕获的变量会变成类对象的成员变量

3、产生的类中有operator()的重载运算符,lambda表达式的参数为重载运算符()的参数,返回值为重载运算符()的返回值

无捕获原理分析

无捕获的会相对复杂一点,无捕获的会涉及到operator的第二种用法

    1、operator重载运算符

    2、operator用于隐式转换,比如operator int(),代表可隐式转换为int类型

class Data {
public:
    Data(int data):innerData{data}{};
    operator int (){
        return innerData;
    };
private:
    int innerData;
}

无捕获举例:

#include <iostream>

int main()
{
    auto doubleLambda = [](int i) -> int {
        return i + i;
    };
    doubleLambda(10);
}

还原为编译后的内容:

#include <iostream>

int main()
{
    
  class __lambda_5_25
  {
    public: 
    inline /*constexpr */ int operator()(int i) const
    {
      return i + i;
    }
    
    using retType_5_25 = auto (*)(int) -> int;
    inline constexpr operator retType_5_25 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline /*constexpr */ int __invoke(int i)
    {
      return __lambda_5_25{}.operator()(i);
    }
    
    
  };
  
  __lambda_5_25 doubleLambda = __lambda_5_25{};
  doubleLambda.operator()(10);
  return 0;
}

格式化后:

#include <iostream>

int main()
{
    
  class __lambda_5_25
  {
    public: 
    // 重载()运算符
    inline  int operator()(int i) const
    {
      return i + i;
    }
    
    // 声明函数指针
    using retType_5_25 = auto (*)(int) -> int;
    // 这里就比较有意思了,operator的第二种用法,隐式转换,operator retType_5_25 (),隐式转换为函数指针
    inline operator retType_5_25 () const 
    {
      return __invoke;
    };
    
    private: 
    // 用私有静态成员函数,作为函数指针
    static inline  int __invoke(int i)
    {
      // 调用对象的operator ()重载的运算符
      return __lambda_5_25{}.operator()(i);
    }
    
    
  };
  
  __lambda_5_25 doubleLambda = __lambda_5_25{};
  doubleLambda.operator()(10);
  return 0;
}

总结:

1、无捕获的表达式会生成隐式转换为”函数指针“的函数,用私有静态函数做承接,并通过生成对象、调用operator()重载运算符内容来完成lambda表达式函数体的调用

问题:是否可以为有捕获的lambda表达式生成隐式转换函数和静态函数?为什么?

答案:不行,因为静态函数在生成类对象时,无法指导捕获的内容,也就无法使用捕获的内容来构造类对象

lambda和函数指针的区别

从前面无捕获的lambda表达式可以看出,无捕获的lambda表达式有转换为函数指针的隐式转换函数,因此

无捕获的lambda表达式可转换为函数指针

有捕获的lambda表达式无法转换为函数指针,因为有捕获的必须传递this指针,导致无法转换

举例说明:

typedef int(*DoubleFunType)(int);

void doAction(DoubleFunType fun)
{
    fun(10);
}

int main()
{
    auto doubleLambda = [](int i) -> int {
        return i + i;
    };
    doAction(doubleLambda);
}

还原编译后的内容:

typedef int(*DoubleFunType)(int);

void doAction(DoubleFunType fun)
{
  fun(10);
}


int main()
{
    
  class __lambda_12_25
  {
    public: 
    inline /*constexpr */ int operator()(int i) const
    {
      return i + i;
    }
    
    using retType_12_25 = auto (*)(int) -> int;
    inline constexpr operator retType_12_25 () const noexcept
    {
      return __invoke;
    }
    
    private: 
    static inline /*constexpr */ int __invoke(int i)
    {
      return __lambda_12_25{}.operator()(i);
    }
    
    
  };
  
  __lambda_12_25 doubleLambda = __lambda_12_25{};
  doAction(doubleLambda.operator __lambda_12_25::retType_12_25());
  return 0;
}

格式化后:

// 声明函数指针
typedef int(*DoubleFunType)(int);

void doAction(DoubleFunType fun)
{
  // 调用函数指针
  fun(10);
}


int main()
{
    // 此处和上面的完全一致
  class __lambda_12_25
  {
    public: 
    inline /*constexpr */ int operator()(int i) const
    {
      return i + i;
    }
    
    using retType_12_25 = auto (*)(int) -> int;
    inline constexpr operator retType_12_25 () const noexcept
    {
      return __invoke;
    }
    
    private: 
    static inline /*constexpr */ int __invoke(int i)
    {
      return __lambda_12_25{}.operator()(i);
    }
    
    
  };
  
  __lambda_12_25 doubleLambda = __lambda_12_25{};
  // doubleLambda.operator retType_12_25() 强制调用隐式转换为函数指针,而__lambda_12_25::是域作用符,用于标识retType_12_25类型是类内部类型
  doAction(doubleLambda.operator __lambda_12_25::retType_12_25());
  return 0;
}

总结:

无捕获的lambda可传递给函数指针。

lambda结合std::function构建轻量级代理

结合业务来说,直接用lambda表达式用于传递函数指针,用处不大,因为无法用有捕获的表达式传给函数指针,无捕获的表达式用处有限

而c++提供一个对象std::function,构造函数可以接收lambda表达式和函数指针,没有转换函数指针的限制,为什么呢?

拿刚刚的例子做下简单修改,把函数指针更改为std::function

typedef std::function<int(int)> DoubleFunType;

void doAction(DoubleFunType fun)
{
    fun(10);
}

int main()
{
    int time = 10;
    auto timeLambda = [time](int i) -> int {
        return time * i;
    };
    doAction(timeLambda);
}

还原后:

#include <iostream>
#include <functional>

typedef std::function<int(int)> DoubleFunType;

void doAction(std::function<int (int)> fun)
{
  // 关键点在这里,std::function对象的执行是只要支持operator()运算符即可,不不是限制于函数指针
  fun.operator()(10);
}


int main()
{
  int time = 10;
    
  class __lambda_14_23
  {
    public: 
    inline /*constexpr */ int operator()(int i) const
    {
      return time * i;
    }
    
    private: 
    int time;
    public: 
    // inline /*constexpr */ __lambda_14_23(const __lambda_14_23 &) noexcept = default;
    __lambda_14_23(int & _time)
    : time{_time}
    {}
    
  };
  
  __lambda_14_23 timeLambda = __lambda_14_23{time};
  doAction(std::function<int (int)>(timeLambda));
  return 0;
}

从注释中可以看出关键点:

std::function对象的执行是只要支持operator()运算符即可,不不是限制于函数指针

因此std::function可以传入带捕获的lambda表达式,从而实现轻量级代理

轻量级代理的设计实现:

https://bbs.huaweicloud.com/blogs/416963

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。