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

文章目录

  • 前言
    • 辅助工具
    • 基本代码
  • 原始方法
    • 函数指针法
    • 仿函数法
    • 分析总结
  • 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 
#define M 10

int main() {
    int arr[M];

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

    return 0;
}

原始方法

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

函数指针法

Source:

#include 
#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 
#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 
#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 
#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 
#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 
#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 
#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 
#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进行了强转

而强转的内容是__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 
#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 
#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

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

Source:

#include 
#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 
#include 
#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

你可能感兴趣的:(C/C++,c++,开发语言,源码软件)