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