文章目录
前言辅助工具基本代码 原始方法函数指针法仿函数法分析总结 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中,auto
和cmp
的自动识别却不一样
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捕获的增强,无状态的构造和复制等等。这里就不再展开例子了。