文章目录
前言
lambda表达式是C++11增加的一个新特性,深受各位开发者的喜爱。
而探究lambda表达式的本质是什么,就是本文的主要内容。
辅助工具
这个网站可以根据源码生成中间代码,辅助学习者学习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中,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 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捕获的增强,无状态的构造和复制等等。这里就不再展开例子了。