(C++) 从stl算法的谓词 分析lambda表达式的本质

文章详细介绍了C++11引入的lambda表达式的概念,以及它如何替代之前的函数指针和仿函数方法。通过std::sort函数的例子,展示了lambda如何直接使用、用auto接收、函数指针接收以及std::function接收的方式,并分析了其内部工作原理。此外,还提及了C++14和C++17/20对lambda表达式的增强,如C++14中的模板参数和C++17以后的this捕获优化。
摘要由CSDN通过智能技术生成

前言

lambda表达式是C++11增加的一个新特性,深受各位开发者的喜爱。

而探究lambda表达式的本质是什么,就是本文的主要内容。

辅助工具

源码探查工具:C++ Insights (cppinsights.io)

这个网站可以根据源码生成中间代码,辅助学习者学习C++的中间过程。这个网站是基于clang实现的。

本文未做特殊说明,默认以C++11标准为例

基本代码

这里就用std::sort()的第三个参数,这里需要的谓词来进行分析。

#include <algorithm>
#define M 10

int main() {
    int arr[M];

    std::sort(arr, arr + M);

    return 0;
}

原始方法

先来看看在C++11之前是怎么实现的

函数指针法

Source:

#include <algorithm>
#define M 10

bool cmp(const int &x, const int &y) {
    return x < y;
}

int main() {
    int arr[M];

    std::sort(arr, arr + M, cmp);

    return 0;
}

Insight:

#include <algorithm>
#define M 10

bool cmp(const int & x, const int & y)
{
  return x < y;
}


int main()
{
  int arr[10];
  std::sort(arr, arr + 10, cmp);
  return 0;
}

仿函数法

Source:

#include <algorithm>
#define M 10

class Cmp {
public:
    bool operator()(const int &x, const int &y) {
        return x < y;
    }
};

int main() {
    int arr[M];

    std::sort(arr, arr + M, Cmp());

    return 0;
}

Insight:

#include <algorithm>
#define M 10

class Cmp
{
  
  public: 
  inline bool operator()(const int & x, const int & y)
  {
    return x < y;
  }
  
  // inline constexpr Cmp(const Cmp &) noexcept = default;
  // inline constexpr Cmp(Cmp &&) noexcept = default;
};



int main()
{
  int arr[10];
  std::sort(arr, arr + 10, Cmp(Cmp()));
  return 0;
}

分析总结

可见在C++11之前,无论是函数指针,还是仿函数,都是比较原始的实现谓词的方式

lambda表达式

直接写在谓词处

开门见山,lambda表达式就是一个具有仿函数的匿名类

展开的代码直接放到本地编译器,也是可以正常编译运行的

由上面的原始方法可知,去掉inline operator retType_7_29 () const noexcept{}static inline bool __invoke(const int & x, const int & y){}可是没问题的

Source:

#include <algorithm>
#define M 10

int main() {
    int arr[M];

    std::sort(arr, arr + M, [](const int &x, const int &y) { return x < y; });

    return 0;
}

Insight:

#include <algorithm>
#define M 10

int main()
{
  int arr[10];
    
  class __lambda_7_29
  {
    public: 
    inline bool operator()(const int & x, const int & y) const
    {
      return x < y;
    }
    
    using retType_7_29 = bool (*)(const int &, const int &);
    inline operator retType_7_29 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline bool __invoke(const int & x, const int & y)
    {
      return __lambda_7_29{}.operator()(x, y);
    }
    
    public: 
    // inline /*constexpr */ __lambda_7_29(const __lambda_7_29 &) noexcept = default;
    // inline /*constexpr */ __lambda_7_29(__lambda_7_29 &&) noexcept = default;
    // inline __lambda_7_29 & operator=(const __lambda_7_29 &) /* noexcept */ = delete;
    
  };
  
  std::sort(arr, arr + 10, __lambda_7_29(__lambda_7_29{}));
  return 0;
}

下面,我们看看不直接写在谓词处,而是用变量接收lambda表达式是怎么样的

auto接收lambda表达式

可见auto识别成了这个匿名对象的类型。

Source:

#include <algorithm>
#define M 10

int main() {
    int arr[M];

    std::sort(arr, arr + M, [](const int &x, const int &y) { return x < y; });

    return 0;
}

Insight:

#include <algorithm>
#define M 10

int main()
{
  int arr[10];
    
  class __lambda_7_16
  {
    public: 
    inline bool operator()(const int & x, const int & y) const
    {
      return x < y;
    }
    
    using retType_7_16 = bool (*)(const int &, const int &);
    inline operator retType_7_16 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline bool __invoke(const int & x, const int & y)
    {
      return __lambda_7_16{}.operator()(x, y);
    }
    
    public: 
    // inline /*constexpr */ __lambda_7_16(const __lambda_7_16 &) noexcept = default;
    // inline /*constexpr */ __lambda_7_16(__lambda_7_16 &&) noexcept = default;
    // inline __lambda_7_16 & operator=(const __lambda_7_16 &) /* noexcept */ = delete;
    
  };
  
  __lambda_7_16 cmp = __lambda_7_16(__lambda_7_16{});
  std::sort(arr, arr + 10, __lambda_7_16(cmp));
  return 0;
}

而在本地的vscode中,autocmp的自动识别却不一样

auto cmp = [](const int &x, const int &y) { return x < y; };

// auto 的自动识别
class lambda [](const int &x, const int &y)->bool
// cmp 的自动识别
bool cmp(const int &x, const int &y)

函数指针接收lambda表达式

当左值的类型确定后,等号右侧若不是完全匹配的配型,一般需要强转

这里很明显的用static_cast<bool (*)(const int &, const int &)>进行了强转

而强转的内容是__lambda_6_45{}.operator __lambda_6_45::retType_6_45()查看源码可见实质是把__invoke这个函数指针传出去了,而__invoke的内容就是调用了仿函数,这是比较常见的一种类的内部保护机制

注意这里是把仿函数的,函数类型指针做了一个符号重载

using retType_6_45 = bool (*)(const int &, const int &);

inline operator retType_6_45 () const noexcept {}

Source:

#include <algorithm>
#define M 10

int main() {
    int arr[M];
    bool (*cmp)(const int &, const int &) = [](const int &x, const int &y) {
        return x < y;
    };
    std::sort(arr, arr + M, cmp);

    return 0;
}

Insight:

#include <algorithm>
#define M 10

int main()
{
  int arr[10];
      
  class __lambda_6_45
  {
    public: 
    inline bool operator()(const int & x, const int & y) const
    {
      return x < y;
    }
    
    using retType_6_45 = bool (*)(const int &, const int &);
    inline operator retType_6_45 () const noexcept
    {
      return __invoke;
    }
    
    private: 
    static inline bool __invoke(const int & x, const int & y)
    {
      return __lambda_6_45{}.operator()(x, y);
    }
    
    
  };
  
  using FuncPtr_6 = bool (*)(const int &, const int &);
  FuncPtr_6 cmp = static_cast<bool (*)(const int &, const int &)>(__lambda_6_45{}.operator __lambda_6_45::retType_6_45());
  std::sort(arr, arr + 10, cmp);
  return 0;
}

std::function接收lambda表达式

函数指针一直是一个比较头痛的内容。在C++11推出了std::function大大改善了接收函数指针的方式

所在头文件:#include <functional>

看的出来,这就是一个再封装一层的对象而已,将lambda的匿名对象,强转成std::function对应的对象

Source:

#include <algorithm>
#define M 10

int main() {
    int arr[M];
    bool (*cmp)(const int &, const int &) = [](const int &x, const int &y) {
        return x < y;
    };
    std::sort(arr, arr + M, cmp);

    return 0;
}

Insight:

#include <algorithm>
#include <functional>
#define M 10

int main()
{
  int arr[10];
    
  class __lambda_7_41
  {
    public: 
    inline bool operator()(const int & x, const int & y) const
    {
      return x < y;
    }
    
    using retType_7_41 = bool (*)(const int &, const int &);
    inline operator retType_7_41 () const noexcept
    {
      return __invoke;
    };
    
    private: 
    static inline bool __invoke(const int & x, const int & y)
    {
      return __lambda_7_41{}.operator()(x, y);
    }
    
    public: 
    // inline /*constexpr */ __lambda_7_41(const __lambda_7_41 &) noexcept = default;
    // inline /*constexpr */ __lambda_7_41(__lambda_7_41 &&) noexcept = default;
    
  };
  
  std::function<bool (int, int)> cmp = std::function<bool (int, int)>(std::function<bool (int, int)>(__lambda_7_41(__lambda_7_41{})));
  std::sort(arr, arr + 10, std::function<bool (int, int)>(cmp));
  return 0;
}

分析总结

用最简单的一句话总结就是,lambda表达式是一个具有仿函数的匿名类

而编译器就是想尽各种办法去调用到这个仿函数

C++11之后的lambda表达式

在C++11之后,C++14,17,20都不断的对lambda表达式进行了优化和增强

下面用C++14的增强举个小例子

C++14增强的例子

在C++14中,可以用auto作为参数实现类似泛型的操作

而查看展开后的源码发现,其实就是对仿函数改为了模板编程

每次不用参数的调用,都会展开一份特化的代码

Source:

int main() {
    int num = 10;
    auto fun = [var = num](auto x) mutable { return x; };

    fun(1);
    fun('a');

    return 0;
}

Insight:

int main()
{
  int num = 10;
    
  class __lambda_3_16
  {
    public: 
    template<class type_parameter_0_0>
    inline auto operator()(type_parameter_0_0 x)
    {
      return x;
    }
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    inline int operator()<int>(int x)
    {
      return x;
    }
    #endif
    
    
    #ifdef INSIGHTS_USE_TEMPLATE
    template<>
    inline char operator()<char>(char x)
    {
      return x;
    }
    #endif
    
    private: 
    int var;
    public: 
    // inline /*constexpr */ __lambda_3_16(__lambda_3_16 &&) noexcept = default;
    __lambda_3_16(int & _var)
    : var{_var}
    {}
    
  };
  
  __lambda_3_16 fun = __lambda_3_16(__lambda_3_16{num});
  fun.operator()(1);
  fun.operator()('a');
  return 0;
}

C++17,20的增强

C++17,20主要是对this捕获的增强,无状态的构造和复制等等。这里就不再展开例子了。




END

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值