Lambda
简介
Lambda提供了一种类似匿名函数的语法特性,常用于简化代码,比如一个遍历字典的函数
void enumerateDict(void (*func) (Key&, Val&)) const;
在调用时就可以写成如下形式, 可见,其类型定义与一个函数指针是相近的
enumerateDict([](string& k, string& v) -> void {
//do something
});
通常还会搭配模板来使用
template <typename _T, typename Less>
void sort(vector<_T>& list, Less const& less);
在需要做特定动作却不想额外声明函数时,也可以使用lambda,那么,如何正确使用这种特性呢?
用法
Lambda表达式的基本语法如下:
[捕获列表](参数列表) 函数修饰选项 -> 返回值类型 {函数体};
主要解释一下捕获列表:它可以通过限定的方式来访问外部作用域中的变量,在栈中一般只能捕获上文变量,视情况而定。
格式 | 作用 |
---|---|
[] | 不进行捕获 |
[=] | 使用拷贝捕获上下文变量 |
[&] | 使用引用的方式捕获~ |
[x] | 拷贝x |
[&x] | 引用x |
[x] | 拷贝x |
[&,x] | 拷贝x,其余的使用引用 |
[=, &x] | 引用x,其余的使用拷贝 |
总而言之,lambda表达式的捕获列表精细控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。
#include <iostream>
#include <typeinfo>
template<typename Arg, typename... Args>
void PrintType(Arg&& arg, Args&&... rest) {
std::cout << typeid(arg).name() << std::endl;
PrintType(std::forward<Args>(rest)...);
}
template<typename Arg>
void PrintType(Arg&& arg) {
std::cout << typeid(arg).name() << std::endl;
}
void TestFunc() {
int nsp = 114514;
std::cout << &nsp << std::endl;
auto lambda = [=]() {
PrintType(&nsp);
std::cout << &nsp << std::endl;
};
auto lambda2 = [&]() {
PrintType(&nsp);
nsp = 1919;
std::cout << &nsp << std::endl;
};
lambda();
lambda2();
std::cout << nsp << std::endl;
}
测试结果如下,捕获方式的区别也体现出来了
另外,当lambda在类中使用时,可以使用[this]捕获当前类中的this指针,此时lambda获得了与成员函数同样的权限。
注意
1.lambda在使用拷贝捕获后修改外部变量时,需要添加mutable来修饰
其实输出下lambda的typeinfo即可发现,这东西的本质还是一个class
auto gLambda = []{};
int main(int argc, char* argv[]) {
auto lambda = []{};
PrintType(lambda, gLambda);
}
class `int __cdecl main(int,char * __ptr64 * __ptr64 const)'::`2'::<lambda_1>
class <lambda_1>
将lambda想象成一个匿名类,它捕获列表中的任何外部变量最终会变为该类的成员变量,而在看似以函数形式调用lambda时使用的operator()在c++标准下受到了const修饰,不能修改成员变量,要解决这个问题,需要在lambda后添加修饰
int nsp = 114514;
//此处不添加mutable,无法通过编译
auto lambda = [=] () mutable {
nsp++;
};
2.为了内存的安全性(?瞎猜的)lambda相互之间无法赋值,但是可以利用已有的lambda来初始化其他的lambda
3.效率问题,最好不要使用[=],[&]来一次性大量捕获变量
特别是对引用的捕获,由于引用捕获不会延长变量的生命周期,所以很有可能被悬挂
int add_x(int x) {
//此处的x看上去被捕获,实际上在return后被销毁,lambda调用的成了垃圾值
return [&](int a) { return x + a; };
}
4.Lambda在满足要求的情况下会隐式转换为constexpr(after c++17)
//位于全局,没有使用静态成员和虚函数,可以隐式转化
auto gLambda = [](auto val) constexpr {
return val * val;
};
//使用了静态成员,如果添加constexpr则无法通过编译(侧面证明了无法进行隐式转换)
auto gsLambda = [](auto val) {
static int test = 0;
return val * val;
};
//上一篇博客里乱写的管理器,func定义为virtual,理论上不能通过编译,但实际可能由于结构比较简单,令编译器优化掉了虚函数调用
//如果有感兴趣的可以尝试关闭vs的编译优化命令研究一下,欢迎交流
auto gvLambda = [](auto val) constexpr {
RAIIObj<VtClass> vtClass;
vtClass->func();
};
int main(int argc, char* argv[]) {
auto lambda = [] {};
auto lambda2 = [=](int temp){};
auto lambda3 = [&](auto nsp){return 0;};
PrintType(lambda, gLambda);
PrintType(lambda, lambda2, lambda3, gLambda, gsLambda, [](){});
std::cout << gLambda(123) << " " << gsLambda(123) << std::endl;
gvLambda(nullptr);
return 0;
}
5.(稀有问题)lambda在成员函数内,调用其他成员函数或变量时,对象可能已经销毁
#include <thread>
#include <chrono>
#include <iostream>
class Test {
public:
Test() = default;
~Test() = default;
std::thread TestProblem() const {
std::thread thd(
[this] {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << data << std::endl;
}
);
return thd;
}
private:
int data = 1;
};
int main(int argc, char* argv[]) {
Test* test = new Test;
std::thread thd = test->TestProblem();
thd.detach();
delete test;
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
为了解决这个问题,在c++17后简单使用*this传入当前类的拷贝即可解决,但在之前的c++标准中是无法使用,不过似乎正常情况下也罢太需要考虑这个问题…