CPP杂记——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;
}

constexpr\color{red}{在测试能否转换,添加constexpr后一定要进行调用}
\color{red}{不然编译器也懒得理咱们}

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++标准中是无法使用,不过似乎正常情况下也罢太需要考虑这个问题…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章