首頁  >  文章  >  後端開發  >  結合C++11新特性來學習C++中lambda表達式的用法

結合C++11新特性來學習C++中lambda表達式的用法

高洛峰
高洛峰原創
2017-01-23 13:57:091574瀏覽

在 C++ 11 中,lambda 表達式(通常稱為 "lambda")是一種在被呼叫的位置或作為參數傳遞給函數的位置定義匿名函數物件的簡單方法。 Lambda 通常用於封裝傳遞給演算法或非同步方法的少量程式碼行。 本文定義了 lambda 是什麼,將 lambda 與其他程式設計技術進行比較,描述其優點,並提供一個基本範例。
Lambda 表達式的各部分
ISO C++ 標準展示了作為第三個參數傳遞給std::sort() 函數的簡單lambda:

#include <algorithm>
#include <cmath>
 
void abssort(float* x, unsigned n) {
 std::sort(x, x + n,
 // Lambda expression begins
 [](float a, float b) {
  return (std::abs(a) < std::abs(b));
 } // end of lambda expression
 );
}

   

此圖顯示了lambdabda 的組成部分:

此圖顯示了lambdabda

結合C++11新特性來學習C++中lambda表達式的用法Capture 子句(在C++ 規格中也稱為lambda 引導。)

參數清單(可選)。 (也稱為 lambda 聲明符)

可變規範(可選)。

異常規範(可選)。

尾隨返回類型(可選)。

「lambda 體」

Capture 子句

Lambda 可在其主體中引入新的變數(以 C++14),它也可以存取(或「捕獲」)週邊範圍內的變數。 Lambda 以 Capture 子句(標準語法中的 lambda 引導)開頭,它指定要捕獲的變數以及是透過值還是引用進行擷取。 有與號 (&) 前綴的變數透過引用訪問,沒有該前綴的變數透過值訪問。

空 capture 子句 [ ] 指示 lambda 表達式的主體不存取封閉範圍中的變數。

可以使用預設擷取模式(標準語法中的 capture-default)來指示如何擷取 lambda 中引用的任何外部變數:[&] 表示透過引用擷取引用的所有變量,而 [=] 表示透過值擷取它們。 可以使用預設擷取模式,然後為特定變數明確指定相反的模式。 例如,如果lambda 體透過引用存取外部變數total 並透過值存取外部變數factor,則以下capture 子句等效:

[&total, factor]
[factor, &total]
[&, factor]
[factor, &]
[=, &total]
[&total, =]

   

使用capture-default 時,只有lambda 中提及的變數才會被提及的變數才會被提及捕獲。

如果 capture 子句包含 capture-default&,則該 capture 子句的 identifier 中沒有任何 capture 可採用 & identifier 形式。 同樣,如果 capture 子句包含 capture-default=,則該 capture 子句的 capture 不能採用 = identifier 形式。 identifier 或 this 在 capture 子句中出現的次數不能超過一次。 以下程式碼片段給出了一些範例。

struct S { void f(int i); };
 
void S::f(int i) {
 [&, i]{}; // OK
 [&, &i]{}; // ERROR: i preceded by & when & is the default
 [=, this]{}; // ERROR: this when = is the default
 [i, i]{}; // ERROR: i repeated
}

 


capture 後跟省略號是包擴展,如以下可變參數模板示例中所示:

template<class... Args>
void f(Args... args) {
 auto x = [args...] { return g(args...); };
 x();
}

要在類方法的正文中使用lambda 表達式,請將Capbda 表達給子句,以提供對封閉類別的方法和資料成員的存取權限。 有關展示如何將 lambda 表達式與類別方法一起使用的範例,請參閱 Lambda 表達式的範例中的「範例:在方法中使用 Lambda 表達式」。

在使用 capture 子句時,建議你記住以下幾點(尤其是使用採取多線程的 lambda 時):

引用捕獲可用於修改外部變量,而值捕獲卻不能實現此操作。 (mutable允許修改副本,而不能修改原始項。)
引用擷取會反映外部變數的更新,而值擷取卻不會反映。
引用捕獲引入生存期依賴項,而值捕獲卻沒有生存期依賴項。 當 lambda 以非同步方式運作時,這一點尤其重要。 如果在非同步 lambda 中透過引用捕獲本地變量,則該本地變數將很可能在 lambda 運行時消失,從而導致運行時存取衝突。

通用捕獲 (C++14)

在 C++14 中,可在 Capture 子句中引入並初始化新的變量,而無需使這些變量存在於 lambda 函數的封閉範圍內。 初始化可以任何任意表達式表示;並且將從該表達式產生的類型推導出新變數的類型。 此功能的一個好處是,在 C++14 中,可從週邊範圍捕獲只移動的變數(例如 std::unique_ptr)並在 lambda 中使用它們。

pNums = make_unique<vector<int>>(nums);
//...
 auto a = [ptr = move(pNums)]()
 {
  // use ptr
 };

參數列表

除了捕獲變量,lambda 還可接受輸入參數。 參數列表(在標準語法中稱為 lambda 聲明符)是可選的,它在大多數方面類似於函數的參數列表。

int y = [] (int first, int second)
{
 return first + second;
};

在 C++14 中,如果參數類型是泛型,則可以使用 auto 關鍵字作為類型說明符。 這將告知編譯器將函數呼叫運算子建立為模板。 參數清單中的每個 auto 實例等效於一個不同的類型參數。

auto y = [] (auto first, auto second)
{
 return first + second;
};

lambda 表达式可以将另一个 lambda 表达式作为其参数。 有关详细信息,请参阅 Lambda 表达式的示例主题中的“高阶 Lambda 表达式”。
由于参数列表是可选的,因此在不将参数传递到 lambda 表达式,并且其 lambda-declarator: 不包含 exception-specification、trailing-return-type 或 mutable 的情况下,可以省略空括号。

可变规范
通常,lambda 的函数调用运算符为 const-by-value,但对 mutable 关键字的使用可将其取消。 它不会生成可变的数据成员。 利用可变规范,lambda 表达式的主体可以修改通过值捕获的变量。 本文后面的一些示例将显示如何使用 mutable。
异常规范
你可以使用 throw() 异常规范来指示 lambda 表达式不会引发任何异常。 与普通函数一样,如果 lambda 表达式声明 C4297 异常规范且 lambda 体引发异常,Visual C++ 编译器将生成警告 throw(),如下所示:

// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
 []() throw() { throw 5; }();
}

返回类型
将自动推导 lambda 表达式的返回类型。 无需使用 auto 关键字,除非指定尾随返回类型。 trailing-return-type 类似于普通方法或函数的返回类型部分。 但是,返回类型必须跟在参数列表的后面,你必须在返回类型前面包含 trailing-return-type 关键字 ->。
如果 lambda 体仅包含一个返回语句或其表达式不返回值,则可以省略 lambda 表达式的返回类型部分。 如果 lambda 体包含单个返回语句,编译器将从返回表达式的类型推导返回类型。 否则,编译器会将返回类型推导为 void。 下面的代码示例片段说明了这一原则。

auto x1 = [](int i){ return i; }; // OK: return type is int
auto x2 = []{ return{ 1, 2 }; }; // ERROR: return type is void, deducing
     // return type from braced-init-list is not valid

   

lambda 表达式可以生成另一个 lambda 表达式作为其返回值。 有关详细信息,请参阅 Lambda 表达式的示例中的“高阶 Lambda 表达式”。
Lambda 体
lambda 表达式的 lambda 体(标准语法中的 compound-statement)可包含普通方法或函数的主体可包含的任何内容。 普通函数和 lambda 表达式的主体均可访问以下变量类型:

从封闭范围捕获变量,如前所述。

参数

本地声明变量

类数据成员(在类内部声明并且捕获 this 时)

具有静态存储持续时间的任何变量(例如,全局变量)

以下示例包含通过值显式捕获变量 n 并通过引用隐式捕获变量 m 的 lambda 表达式:

// captures_lambda_expression.cpp
// compile with: /W4 /EHsc
#include <iostream>
using namespace std;
 
int main()
{
 int m = 0;
 int n = 0;
 [&, n] (int a) mutable { m = ++n + a; }(4);
 cout << m << endl << n << endl;
}

   

输出:

  5
0

            
由于变量 n 是通过值捕获的,因此在调用 lambda 表达式后,变量的值仍保持 0 不变。 mutable 规范允许在 lambda 中修改 n。
尽管 lambda 表达式只能捕获具有自动存储持续时间的变量,但你可以在 lambda 表达式的主体中使用具有静态存储持续时间的变量。 以下示例使用 generate 函数和 lambda 表达式为 vector 对象中的每个元素赋值。 lambda 表达式将修改静态变量以生成下一个元素的值。

void fillVector(vector<int>& v)
{
 // A local static variable.
 static int nextValue = 1;
 
 // The lambda expression that appears in the following call to
 // the generate function modifies and uses the local static
 // variable nextValue.
 generate(v.begin(), v.end(), [] { return nextValue++; });
 //WARNING: this is not thread-safe and is shown for illustration only
}

   

下面的代码示例使用上一示例中的函数,并添加了使用 STL 算法 generate_n 的 lambda 表达式的示例。 该 lambda 表达式将 vector 对象的元素指派给前两个元素之和。 使用了 mutable 关键字,以使 lambda 表达式的主体可以修改 lambda 表达式通过值捕获的外部变量 x 和 y 的副本。 由于 lambda 表达式通过值捕获原始变量 x 和 y,因此它们的值在 lambda 执行后仍为 1。

// compile with: /W4 /EHsc
#include 
#include 
#include 
#include 
 
using namespace std;
 
template  void print(const string& s, const C& c) {
 cout << s;
 
 for (const auto& e : c) {
 cout << e << " ";
 }
 
 cout << endl;
}
 
void fillVector(vector<int>& v)
{
 // A local static variable.
 static int nextValue = 1;
 
 // The lambda expression that appears in the following call to
 // the generate function modifies and uses the local static
 // variable nextValue.
 generate(v.begin(), v.end(), [] { return nextValue++; });
 //WARNING: this is not thread-safe and is shown for illustration only
}
 
int main()
{
 // The number of elements in the vector.
 const int elementCount = 9;
 
 // Create a vector object with each element set to 1.
 vector v(elementCount, 1);
 
 // These variables hold the previous two elements of the vector.
 int x = 1;
 int y = 1;
 
 // Sets each element in the vector to the sum of the
 // previous two elements.
 generate_n(v.begin() + 2,
 elementCount - 2,
 [=]() mutable throw() -> int { // lambda is the 3rd parameter
 // Generate current value.
 int n = x + y;
 // Update previous two values.
 x = y;
 y = n;
 return n;
 });
 print("vector v after call to generate_n() with lambda: ", v);
 
 // Print the local variables x and y.
 // The values of x and y hold their initial values because
 // they are captured by value.
 cout << "x: " << x << " y: " << y << endl;
 
 // Fill the vector with a sequence of numbers
 fillVector(v);
 print("vector v after 1st call to fillVector(): ", v);
 // Fill the vector with the next sequence of numbers
 fillVector(v);
 print("vector v after 2nd call to fillVector(): ", v);
}

   

输出:

  vector v after call to generate_n() with lambda: 1 1 2 3 5 8 13 21 34
x: 1 y: 1
vector v after 1st call to fillVector(): 1 2 3 4 5 6 7 8 9
vector v after 2nd call to fillVector(): 10 11 12 13 14 15 16 17 18

更多結合C++11新特性來學習C++中lambda表達式的用法相关文章请关注PHP中文网!   


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn