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++標準中是無法使用,不過似乎正常情況下也罷太需要考慮這個問題…

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