鍍金池/ 教程/ C/ A.5 Lambda函數(shù)
3.4 本章總結(jié)
6.3 基于鎖設(shè)計更加復(fù)雜的數(shù)據(jù)結(jié)構(gòu)
6.1 為并發(fā)設(shè)計的意義何在?
5.2 <code>C++</code>中的原子操作和原子類型
A.7 自動推導(dǎo)變量類型
2.1 線程管理的基礎(chǔ)
8.5 在實踐中設(shè)計并發(fā)代碼
2.4 運行時決定線程數(shù)量
2.2 向線程函數(shù)傳遞參數(shù)
第4章 同步并發(fā)操作
2.3 轉(zhuǎn)移線程所有權(quán)
8.3 為多線程性能設(shè)計數(shù)據(jù)結(jié)構(gòu)
6.4 本章總結(jié)
7.3 對于設(shè)計無鎖數(shù)據(jù)結(jié)構(gòu)的指導(dǎo)建議
關(guān)于這本書
A.1 右值引用
2.6 本章總結(jié)
D.2 &lt;condition_variable&gt;頭文件
A.6 變參模板
6.2 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)
4.5 本章總結(jié)
A.9 本章總結(jié)
前言
第10章 多線程程序的測試和調(diào)試
5.4 本章總結(jié)
第9章 高級線程管理
5.1 內(nèi)存模型基礎(chǔ)
2.5 識別線程
第1章 你好,C++的并發(fā)世界!
1.2 為什么使用并發(fā)?
A.5 Lambda函數(shù)
第2章 線程管理
4.3 限定等待時間
D.3 &lt;atomic&gt;頭文件
10.2 定位并發(fā)錯誤的技術(shù)
附錄B 并發(fā)庫的簡單比較
5.3 同步操作和強制排序
A.8 線程本地變量
第8章 并發(fā)代碼設(shè)計
3.3 保護(hù)共享數(shù)據(jù)的替代設(shè)施
附錄D C++線程庫參考
第7章 無鎖并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計
D.7 &lt;thread&gt;頭文件
D.1 &lt;chrono&gt;頭文件
4.1 等待一個事件或其他條件
A.3 默認(rèn)函數(shù)
附錄A 對<code>C++</code>11語言特性的簡要介紹
第6章 基于鎖的并發(fā)數(shù)據(jù)結(jié)構(gòu)設(shè)計
封面圖片介紹
7.2 無鎖數(shù)據(jù)結(jié)構(gòu)的例子
8.6 本章總結(jié)
8.1 線程間劃分工作的技術(shù)
4.2 使用期望等待一次性事件
8.4 設(shè)計并發(fā)代碼的注意事項
D.5 &lt;mutex&gt;頭文件
3.1 共享數(shù)據(jù)帶來的問題
資源
9.3 本章總結(jié)
10.3 本章總結(jié)
10.1 與并發(fā)相關(guān)的錯誤類型
D.4 &lt;future&gt;頭文件
3.2 使用互斥量保護(hù)共享數(shù)據(jù)
9.1 線程池
1.1 何謂并發(fā)
9.2 中斷線程
4.4 使用同步操作簡化代碼
A.2 刪除函數(shù)
1.3 C++中的并發(fā)和多線程
1.4 開始入門
第5章 C++內(nèi)存模型和原子類型操作
消息傳遞框架與完整的ATM示例
8.2 影響并發(fā)代碼性能的因素
7.1 定義和意義
D.6 &lt;ratio&gt;頭文件
A.4 常量表達(dá)式函數(shù)
7.4 本章總結(jié)
1.5 本章總結(jié)
第3章 線程間共享數(shù)據(jù)

A.5 Lambda函數(shù)

lambda函數(shù)在C++11中的加入很是令人興奮,因為lambda函數(shù)能夠大大簡化代碼復(fù)雜度(語法糖:利于理解具體的功能),避免實現(xiàn)調(diào)用對象。C++11的lambda函數(shù)語法允許在需要使用的時候進(jìn)行定義。能為等待函數(shù),例如std::condition_variable(如同4.1.1節(jié)中的例子)提供很好謂詞函數(shù),其語義可以用來快速的表示可訪問的變量,而非使用類中函數(shù)來對成員變量進(jìn)行捕獲。

最簡單的情況下,lambda表達(dá)式就一個自給自足的函數(shù),不需要傳入函數(shù)僅依賴管局變量和函數(shù),甚至都可以不用返回一個值。這樣的lambda表達(dá)式的一系列語義都需要封閉在括號中,還要以方括號作為前綴:

[]{  // lambda表達(dá)式以[]開始
  do_stuff();
  do_more_stuff();
}();  // 表達(dá)式結(jié)束,可以直接調(diào)用

例子中,lambda表達(dá)式通過后面的括號調(diào)用,不過這種方式不常用。一方面,如果想要直接調(diào)用,可以在寫完對應(yīng)的語句后,就對函數(shù)進(jìn)行調(diào)用。對于函數(shù)模板,傳遞一個參數(shù)進(jìn)去時很常見的事情,甚至可以將可調(diào)用對象作為其參數(shù)傳入;可調(diào)用對象通常也需要一些參數(shù),或返回一個值,亦或兩者都有。如果想給lambda函數(shù)傳遞參數(shù),可以參考下面的lambda函數(shù),其使用起來就像是一個普通函數(shù)。例如,下面代碼是將vector中的元素使用std::cout進(jìn)行打?。?/p>

std::vector<int> data=make_data();
std::for_each(data.begin(),data.end(),[](int i){std::cout<<i<<"\n";});

返回值也是很簡單的,當(dāng)lambda函數(shù)體包括一個return語句,返回值的類型就作為lambda表達(dá)式的返回類型。例如,使用一個簡單的lambda函數(shù)來等待std::condition_variable(見4.1.1節(jié))中的標(biāo)志被設(shè)置。

清單A.4 lambda函數(shù)推導(dǎo)返回類型

std::condition_variable cond;
bool data_ready;
std::mutex m;
void wait_for_data()
{
  std::unique_lock<std::mutex> lk(m);
  cond.wait(lk,[]{return data_ready;});  // 1
}

lambda的返回值傳遞給cond.wait()①,函數(shù)就能推斷出data_ready的類型是bool。當(dāng)條件變量從等待中蘇醒后,上鎖階段會調(diào)用lambda函數(shù),并且當(dāng)data_ready為true時,僅返回到wait()中。

當(dāng)lambda函數(shù)體中有多個return語句,就需要顯式的指定返回類型。只有一個返回語句的時候,也可以這樣做,不過這樣可能會讓你的lambda函數(shù)體看起來更復(fù)雜。返回類型可以使用跟在參數(shù)列表后面的箭頭(->)進(jìn)行設(shè)置。如果lambda函數(shù)沒有任何參數(shù),還需要包含(空)的參數(shù)列表,這樣做是為了能顯式的對返回類型進(jìn)行指定。對條件變量的預(yù)測可以寫成下面這種方式:

cond.wait(lk,[]()->bool{return data_ready;});

還可以對lambda函數(shù)進(jìn)行擴(kuò)展,比如:加上log信息的打印,或做更加復(fù)雜的操作:

cond.wait(lk,[]()->bool{
  if(data_ready)
  {
    std::cout<<”Data ready”<<std::endl;
    return true;
  }
  else
  {
    std::cout<<”Data not ready, resuming wait”<<std::endl;
    return false;
  }
});

雖然簡單的lambda函數(shù)很強大,能簡化代碼,不過其真正的強大的地方在于對本地變量的捕獲。

A.5.1 引用本地變量的Lambda函數(shù)

lambda函數(shù)使用空的[](lambda introducer)就不能引用當(dāng)前范圍內(nèi)的本地變量;其只能使用全局變量,或?qū)⑵渌狄詤?shù)的形式進(jìn)行傳遞。當(dāng)想要訪問一個本地變量,需要對其進(jìn)行捕獲。最簡單的方式就是將范圍內(nèi)的所有本地變量都進(jìn)行捕獲,使用[=]就可以完成這樣的功能。函數(shù)被創(chuàng)建的時候,就能對本地變量的副本進(jìn)行訪問了。

實踐一下,看一下下面的例子:

std::function<int(int)> make_offseter(int offset)
{
  return [=](int j){return offset+j;};
}

當(dāng)調(diào)用make_offseter時,就會通過std::function<>函數(shù)包裝返回一個新的lambda函數(shù)體。

這個帶有返回的函數(shù)添加了對參數(shù)的偏移功能。例如:

int main()
{
  std::function<int(int)> offset_42=make_offseter(42);
  std::function<int(int)> offset_123=make_offseter(123);
  std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
  std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
}

屏幕上將打印出54,135兩次,因為第一次從make_offseter中返回,都是對參數(shù)加42的;第二次調(diào)用后,make_offseter會對參數(shù)加上123。所以,會打印兩次相同的值。

這種本地變量捕獲的方式相當(dāng)安全,所有的東西都進(jìn)行了拷貝,所以可以通過lambda函數(shù)對表達(dá)式的值進(jìn)行返回,并且可在原始函數(shù)之外的地方對其進(jìn)行調(diào)用。這也不是唯一的選擇,也可以通過選擇通過引用的方式捕獲本地變量。在本地變量被銷毀的時候,lambda函數(shù)會出現(xiàn)未定義的行為。

下面的例子,就介紹一下怎么使用[&]對所有本地變量進(jìn)行引用:

int main()
{
  int offset=42;  // 1
  std::function<int(int)> offset_a=[&](int j){return offset+j;};  // 2
  offset=123;  // 3
  std::function<int(int)> offset_b=[&](int j){return offset+j;};  // 4
  std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl;  // 5
  offset=99;  // 6
  std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl;  // 7
}

之前的例子中,使用[=]來對要偏移的變量進(jìn)行拷貝,offset_a函數(shù)就是個使用[&]捕獲offset的引用的例子②。所以,offset初始化成42也沒什么關(guān)系①;offset_a(12)的例子通常會依賴與當(dāng)前offset的值。在③上,offset的值會變?yōu)?23,offset_b④函數(shù)將會使用到這個值,同樣第二個函數(shù)也是使用引用的方式。

現(xiàn)在,第一行打印信息⑤,offset為123,所以輸出為135,135。不過,第二行打印信息⑦就有所不同,offset變成99⑥,所以輸出為111,111。offset_a和offset_b都對當(dāng)前值進(jìn)行了加12的操作。

塵歸塵,土歸土,C++還是C++;這些選項不會讓你感覺到特別困惑,你可以選擇以引用或拷貝的方式對變量進(jìn)行捕獲,并且你還可以通過調(diào)整中括號中的表達(dá)式,來對特定的變量進(jìn)行顯式捕獲。如果想要拷貝所有變量,而非一兩個,可以使用[=],通過參考中括號中的符號,對變量進(jìn)行捕獲。下面的例子將會打印出1239,因為i是拷貝進(jìn)lambda函數(shù)中的,而j和k是通過引用的方式進(jìn)行捕獲的:

int main()
{
  int i=1234,j=5678,k=9;
  std::function<int()> f=[=,&j,&k]{return i+j+k;};
  i=1;
  j=2;
  k=3;
  std::cout<<f()<<std::endl;
}

或者,也可以通過默認(rèn)引用方式對一些變量做引用,而對一些特別的變量進(jìn)行拷貝。這種情況下,就要使用[&]與拷貝符號相結(jié)合的方式對列表中的變量進(jìn)行拷貝捕獲。下面的例子將打印出5688,因為i通過引用捕獲,但j和k 通過拷貝捕獲:

int main()
{
  int i=1234,j=5678,k=9;
  std::function<int()> f=[&,j,k]{return i+j+k;};
  i=1;
  j=2;
  k=3;
  std::cout<<f()<<std::endl;
}

如果你只想捕獲某些變量,那么你可以忽略=或&,僅使用變量名進(jìn)行捕獲就行;加上&前綴,是將對應(yīng)變量以引用的方式進(jìn)行捕獲,而非拷貝的方式。下面的例子將打印出5682,因為i和k是通過引用的范式獲取的,而j是通過拷貝的方式:

int main()
{
  int i=1234,j=5678,k=9;
  std::function<int()> f=[&i,j,&k]{return i+j+k;};
  i=1;
  j=2;
  k=3;
  std::cout<<f()<<std::endl;
}

最后一種方式,是為了確保預(yù)期的變量能被捕獲,在捕獲列表中引用任何不存在的變量都會引起編譯錯誤。當(dāng)選擇這種方式,就要小心類成員的訪問方式,確定類中是否包含一個lambda函數(shù)的成員變量。類成員變量不能直接捕獲,如果想通過lambda方式訪問類中的成員,需要在捕獲列表中添加this指針,以便捕獲。下面的例子中,lambda捕獲this后,就能訪問到some_data類中的成員:

struct X
{
  int some_data;
  void foo(std::vector<int>& vec)
  {
    std::for_each(vec.begin(),vec.end(),
         [this](int& i){i+=some_data;});
  }
};

并發(fā)的上下文中,lambda是很有用的,其可以作為謂詞放在std::condition_variable::wait()(見4.1.1節(jié))和std::packaged_task<>(見4.2.1節(jié))中;或是用在線程池中,對小任務(wù)進(jìn)行打包。也可以線程函數(shù)的方式std::thread的構(gòu)造函數(shù)(見2.1.1),以及作為一個并行算法實現(xiàn),在parallel_for_each()(見8.5.1節(jié))中使用。