匿名函數 lambda表達式(lambda expression)


閱讀g2log時,發現有兩行代碼居然看不懂。

1. auto bg_call =  [this, log_directory]() {return pimpl_->backgroundChangeLogFile(log_directory);};

2. auto bg_call = [&]() {return pimpl_->backgroundFileName();};

 

https://zh.wikipedia.org/wiki/%E5%8C%BF%E5%90%8D%E5%87%BD%E6%95%B0#C.2B.2B 中有所描述

C++ 98/03

C++ 98/03標准並不原生支持匿名函數。不過可以利用Boost庫的Boost.Lambda來實現一個匿名函數。

C++ 11

C++11標准提供了匿名函數的支持,在《ISO/IEC 14882:2011》(C++11標准文檔)中叫做lambda表達式。一個lambda表達式有如下的形式:

[capture] (parameters) mutable exception attribute -> return_type { body }

必須用方括號括起來的capture列表來開始一個lambda表達式的定義。

lambda函數的形參表比普通函數的形參表多了3條限制:

  1. 參數不能有缺省值
  2. 不能有可變長參數列表
  3. 不能有無名參數

如果lambda函數沒有形參且沒有mutable、exception或attribute聲明,那么參數的空圓括號可以省略。但如果需要給出mutable、exception或attribute聲明,那么參數即使為空,圓括號也不能省略。

如果函數體只有一個return語句,或者返回值類型為void,那么返回值類型聲明可以被省略:

[capture](parameters){body}

一個lambda函數的例子如下:

[](int x, int y) { return x + y; } // 從return語句中隱式獲得的返回值類型
[](int& x) { ++x; }   // 沒有return語句 -> lambda函數的返回值為void
[]() { ++global_x; }  // 沒有參數,僅僅是訪問一個全局變量
[]{ ++global_x; }     // 與前者相同,()可以被省略

在上面的第一個例子中這個無名函數的返回值是decltype(x+y)。如果lambda函數體的形式是return expression,或者什么也沒返回,或者所有返回語句用decltype都能檢測到同一類型,那么返回值類型可以被省略。

返回值類型可以顯式指定,如下所示:

[](int x, int y) -> int { int z = x + y; return z; }

在這個例子中,一個臨時變量,z,被創建來存儲中間過程。與一般的函數一樣,中間值在調用的前后並不存在。什么也沒有返回的lambda表達式無需顯式指定返回值,沒有必要寫-> void代碼。

lambda函數可以捕獲lambda函數外的具有automatic storage duration的變量,即函數的局部變量與函數形參變量。函數體與這些變量的集合合起來稱做閉包。這些外部變量在聲明lambda表達式時列在在方括號[]中。空的方括號表示沒有外界變量被capture或者按照默認方式捕獲外界變量。這些變量被傳值捕獲或者引用捕獲。對於傳值捕獲的變量,默認為只讀(這是由於lambda表達式生成的為一個函數對象,它的operator()成員缺省有const屬性)。修改這些傳值捕獲變量將導致編譯報錯。但在lambda表達式的參數表的圓括號后面使用mutable關鍵字,就允許lambda函數體內的語句修改傳值捕獲變量,這些修改與lambda表達式(實際上是用函數對象實現)有相同的生命期,但不影響被傳值捕獲的外部變量的值。lambda函數可以直接使用具有static存儲期的變量。如果在lambda函數的捕獲列表中給出了static存儲期的變量,編譯時會給出警告,仍然按照lambda函數直接使用這些外部變量來處理。因此具有static存儲期的變量即使被聲明為傳值捕獲,修改該變量實際上直接修改了這些外部變量。編譯器生成lambda函數對應的函數對象時,不會用函數對象的數據成員來保持被“捕獲”的static存儲期的變量。示例:

[]        // 沒有定義任何變量,但必須列出空的方括號。在Lambda表達式中嘗試使用任何外部變量都會導致編譯錯誤。
[x, &y]   // x是按值傳遞,y是按引用傳遞
[&]       // 任何被使用到的外部變量都按引用傳入。
[=]       // 任何被使用到的外部變量都按值傳入。
[&, x]    // x按值傳入。其它變量按引用傳入。
[=, &z]   // z按引用傳入。其它變量按值傳入。

下面這個例子展示了lambda表達式的使用:

std::vector<int> some_list{ 1, 2, 3, 4, 5 };
int total = 0;
std::for_each(begin(some_list), end(some_list), [&total](int x) {  total += x; } );
在類的非靜態成員函數中定義的lambda表達式可以顯式或隱式捕捉this指針,從而可以引用所在類對象的數據成員與函數成員。

lambda函數的函數體中,可以訪問下述變量:

  • 函數參數
  • 局部聲明的變量
  • 類數據成員(當lambda表達式聲明在類中),需要注意的是實際上捕獲了對象的this指針。
  • 具有靜態存儲期的變量(如全局變量)
  • 被捕獲的外部變量
    • 顯式捕獲的變量
    • 隱式捕獲的變量,使用默認捕獲模式(傳值或引用)來訪問。

lambda函數的數據類型是函數對象,保存時必須用std::function模板類型或auto關鍵字。 例如:

#include <functional>
#include <vector>
#include <iostream>

double eval(std::function <double(double)> f, double x = 2.0)
{
	return f(x);
}

int main()
{
	std::function<double(double)> f0    = [](double x){return 1;};
	auto                          f1    = [](double x){return x;};
	decltype(f0)                  fa[3] = {f0,f1,[](double x){return x*x;}};
	std::vector<decltype(f0)>     fv    = {f0,f1};
	fv.push_back                  ([](double x){return x*x;});
	for(int i=0;i<fv.size();i++)
		std::cout << fv[i](2.0) << std::endl;
	for(int i=0;i<3;i++)
		std::cout << fa[i](2.0) << std::endl;
	for(auto &f : fv)
		std::cout << f(2.0) << std::endl;
	for(auto &f : fa)
		std::cout << f(2.0) << std::endl;
	std::cout << eval(f0) << std::endl;
	std::cout << eval(f1) << std::endl;
	std::cout << eval([](double x){return x*x;}) << std::endl;
	return 0;
}

一個lambda函數的捕捉表達式為空,則可以用普通函數指針存儲或調用。例如:

auto a_lambda_func = [](int x) { /*...*/ };
void (* func_ptr)(int) = a_lambda_func;
func_ptr(4); //calls the lambda.

C++14

C++14增加了初始捕獲(init capture)。由於lambda表達式實際構造了一個函數對象,初始捕獲實質是初始化了函數對象的成員。C++14還允許lambda函數的形參使用auto關鍵字作為其類型,這實質上是函數對象的operator()成員作為模板函數;並且允許可變參數模板

auto a_lambda_func = [data1=101](int x) { /*...*/ }; //初始捕獲,實質上是在函數對象增加了數據成員data1並初始化

auto ptr = std::make_unique<int>(10); //See below for std::make_unique
auto lambda1 = [ptr = std::move(ptr)] {return *ptr;}
          //大致等效於:   
auto lambda2 = [ptr = std::make_unique<int>(10)] {return *ptr;}

auto lambda3 = [](auto x, auto y) {return x + y;} //lambda函數的形參類型為auto
struct unnamed_lambda                            //這相當於函數對象:
{
  template<typename T, typename U>
    auto operator()(T x, U y) const {return x + y;}
};

auto lambda4 = [](auto&&... params)             //可變參數的函數模板
{
     return (foo(std::forward<decltype(params)>(params)...));
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM