当前位置:首页 » 《随便一记》 » 正文

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

9 人参与  2023年02月03日 10:25  分类 : 《随便一记》  评论

点击全文阅读


文章目录

前言辅助工具基本代码 原始方法函数指针法仿函数法分析总结 lambda表达式直接写在谓词处auto接收lambda表达式函数指针接收lambda表达式std::function接收lambda表达式分析总结 C++11之后的lambda表达式C++14增强的例子C++17,20的增强 END

前言

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

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

辅助工具

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

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

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

基本代码

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

#include <algorithm>#define M 10int main() {    int arr[M];    std::sort(arr, arr + M);    return 0;}

原始方法

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

函数指针法

Source:

#include <algorithm>#define M 10bool 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 10bool 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 10class 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 10class 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 10int 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 10int 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 10int 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 10int 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 10int 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 10int 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 10int 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 10int 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


点击全文阅读


本文链接:http://zhangshiyu.com/post/52415.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1