C++ 中的 Lambda 運算式
在 C++11 和更新版本中,Lambda 運算式通常稱為 Lambda,是定義匿名函式物件( 關閉)的便利方式,位於其叫用或傳遞為函式自變數的位置。 通常 Lambda 是用來封裝幾行程式代碼,這些程式代碼會傳遞至演算法或異步函式。 本文會定義什麼是 Lambda,並將其與其他程式設計技術進行比較。 其描述其優點,並提供一些基本範例。
相關文章
Lambda 表達式的元件
以下是作為第三個自變數傳遞至函式的 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
);
}
此圖顯示 Lambda 語法的部分:
Lambda 表達式範例為 [=]() 可變 throw() -> int { return x+y; }[=] 是擷取子句;也稱為 C++ 規格中的 Lambda 引進器。 括號適用於參數清單。 可變動關鍵詞是選擇性的。 throw() 是選擇性的例外狀況規格。 -> int 是選擇性的尾端傳回型別。 Lambda 主體包含大括弧內的 語句,或傳回 x+y;這些會在影像之後更詳細地說明。
capture 子句 (也稱為 C++ 規格中的 lambda-introducer 。
參數清單 選用。 (也稱為 Lambda 宣告子)
可變動規格 選用。
exception-specification 選用。
trailing-return-type 選擇性。
Lambda 主體。
擷取
Lambda 可以在其主體中引進新的變數(在 C++14 中),也可以從周圍範圍存取或 擷取變數。 Lambda 會從擷取子句開始。 它會指定擷取哪些變數,以及擷取是依值還是以傳址方式擷取。 具有 ampersand (&
) 前置詞的變數會依傳址方式存取,而沒有該前置詞的變數則依值存取。
空白的擷取子句 [ ]
表示 Lambda 運算式主體不存取封閉範圍中的任何變數。
您可以使用擷取預設模式來指出如何擷取 Lambda 主體中所參考的任何外部變數: [&]
表示您參考的所有變數都會依傳址方式擷取,並 [=]
表示這些變數是以傳值方式擷取。 您可以使用預設擷取模式,然後明確指定特定變數的相反模式。 例如,如果 Lambda 主體以傳址方式存取外部變數 total
,並以傳值方式存取外部變數 factor
,則下列擷取子句相等:
[&total, factor]
[factor, &total]
[&, factor]
[=, &total]
使用擷取預設值時,只會擷取 Lambda 主體中提及的變數。
如果擷取子句包含擷取預設值 &
,則擷取子句中沒有任何標識碼可以有 格式 &identifier
。 同樣地,如果擷取子句包含擷取預設值 =
,則該擷取子句的擷取格式不能有 =identifier
。 擷取子句中的標識碼或 this
不能多次出現。 下列代碼段說明一些範例:
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
[=, *this]{ }; // OK: captures this by value. See below.
[i, i]{}; // ERROR: i repeated
}
擷取後面接著省略號是套件擴充,如下列 variadic 範 例所示:
template<class... Args>
void f(Args... args) {
auto x = [args...] { return g(args...); };
x();
}
若要在類別成員函式主體中使用 Lambda 運算式,請傳遞 this
擷取子句的指標,以提供封入類別成員函式和數據成員的存取權。
Visual Studio 2017 15.3 版和更新版本(適用於/std:c++17
模式和更新版本):this
指標可藉由在擷取子句中指定*this
來擷取。 依值擷取會將整個關閉複製到叫用 Lambda 的每個呼叫月臺。 (關閉是封裝 Lambda 表達式的匿名函式物件。當 Lambda 以平行或異步操作執行時,依值擷取會很有用。 它特別適用於某些硬體架構,例如 NUMA。
如需示範如何搭配類別成員函式使用 Lambda 表達式的範例,請參閱 Lambda 運算式範例中的 <範例:在方法中使用 lambda="">範例:在方法中使用>>。
當您使用擷取子句時,建議您記住這些點,特別是當您搭配多線程使用 Lambda 時:
參考擷取可用來修改外部變數,但值擷取無法。 (
mutable
允許修改複本,但不允許原始。參考擷取會反映外部變數的更新,但值擷取不會。
傳址擷取方式採用存留期相依性,但傳值擷取方式沒有存留期相依性。 當 Lambda 以異步方式執行時,特別重要。 如果您在異步 Lambda 中以傳址方式擷取本機,該本機可能會在 Lambda 執行時輕鬆消失。 您的程式代碼可能會在運行時間造成存取違規。
一般化擷取 (C++14)
在 C++14 中,您可以在擷取子句中引進和初始化新的變數,而不需要在 Lambda 函式的封入範圍中存在這些變數。 初始化可以表示為任何任意運算式;新變數的類型是透過運算式所產生的類型推斷而來。 這項功能可讓您從周圍範圍擷取僅限移動變數(例如 std::unique_ptr
),並在 Lambda 中使用它們。
pNums = make_unique<vector<int>>(nums);
//...
auto a = [ptr = move(pNums)]()
{
// use ptr
};
參數清單
Lambda 可以同時擷取變數並接受輸入參數。 參數清單(Standard 語法中的 Lambda 宣告子 )是選擇性的,而且在大部分方面類似於函式的參數清單。
auto 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 宣告子不包含 exception-specification、 trailing-return-type 或 mutable
,則可以省略空括號。
可變動規格
一般而言,Lambda 的函式呼叫運算符是 const-by-value,但使用 mutable
關鍵詞會取消這項作業。它不會產生可變的數據成員。 此 mutable
規格可讓 Lambda 表達式的主體修改值所擷取的變數。 本文稍後的一些範例示範如何使用 mutable
。
例外狀況規格
您可以使用 noexcept
例外狀況規格來指出 Lambda 表達式不會擲回任何例外狀況。 如同一般函式,如果 Lambda 表達式宣告noexcept
例外狀況規格,且 Lambda 主體擲回例外狀況,Microsoft C++ 編譯程式會產生警告 C4297,如下所示:
// throw_lambda_expression.cpp
// compile with: /W4 /EHsc
int main() // C4297 expected
{
[]() noexcept { throw 5; }();
}
如需詳細資訊,請參閱 例外狀況規格 (throw)。
傳回類型
Lambda 運算式的傳回型別會自動推算出來。 除非您指定尾端傳回型別,否則不需要使用 auto
關鍵詞。 尾端傳回型別類似於一般函式或成員函式的傳回類型部分。 不過,傳回型別必須接在參數清單後面,而且您必須在傳回型別前面包含 trailing-return-type 關鍵字 ->
。
如果 Lambda 主體只包含一個 return 語句,則可以省略 Lambda 表達式的傳回類型部分。 或者,如果表示式未傳回值,則為 。 如果 Lambda 主體包含單一 return 陳述式,編譯器會從傳回運算式的類型推算傳回型別。 否則,編譯程式會將傳回型別推算為 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 isn't valid
Lambda 運算式可能會產生另一個 Lambda 運算式當做其傳回值。 如需詳細資訊,請參閱 Lambda 運算式範例中的 <較高順序 lambda="">較高順序>>。
Lambda 主體
Lambda 表達式的 Lambda 主體是複合語句。 它可以包含一般函式或成員函式主體中允許的任何專案。 一般函式和 Lambda 運算式的主體都可以存取下列類型的變數:
擷取自封閉範圍的變數 (如先前所述)。
參數。
本機宣告的變數。
類別數據成員,在類別內宣告並
this
擷取時。任何具有靜態儲存持續時間的變數,例如全域變數。
下列範例包含 Lambda 運算式,這個運算式會以傳值方式明確擷取變數 n
,並以傳址方式隱含擷取變數 m
:
// 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
允許 n
在 Lambda 內修改。
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 isn't thread-safe and is shown for illustration only
}
如需詳細資訊,請參閱 產生。
下列程式代碼範例會使用上一個範例中的 函式,並新增使用 C++ 標準連結庫演算法 generate_n
的 Lambda 運算式範例。 此 Lambda 運算式會將 vector
物件的元素指派給前兩個元素的總和。 使用 mutable
關鍵詞,讓 Lambda 表達式的主體可以修改其外部變數和 的複本,而 Lambda 運算式會依值擷取這些變數x
y
。 由於 Lambda 運算式會以傳值方式擷取原始變數 x
和 y
,在 Lambda 執行後,它們的值仍然保持 1
。
// compile with: /W4 /EHsc
#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
template <typename C> 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 isn't 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<int> 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
如需詳細資訊,請參閱 generate_n。
constexpr
Lambda 表達式
Visual Studio 2017 15.3 版和更新版本 (可在 /std:c++17
模式和更新版本中使用):您可以在常數表達式中允許每個擷取或引進的數據成員初始化時,將 Lambda 表達式宣告為 constexpr
(或在常數運算式中使用)。
int y = 32;
auto answer = [y]() constexpr
{
int x = 10;
return y + x;
};
constexpr int Increment(int n)
{
return [n] { return n + 1; }();
}
如果 Lambda 的結果符合函式的需求,constexpr
則為隱含constexpr
方式:
auto answer = [](int n)
{
return 32 + n;
};
constexpr int response = answer(10);
如果 Lambda 隱含或明確 constexpr
,則轉換成函式指標會產生函 constexpr
式:
auto Increment = [](int n)
{
return n + 1;
};
constexpr int(*inc)(int) = Increment;
Microsoft 專有
下列 Common Language Runtime (CLR) Managed 實體不支援 Lambda:ref class
、、 ref struct
value class
或 value struct
。
如果您使用 Microsoft 特定的修飾詞,例如 __declspec
,您可以在 之後 parameter-declaration-clause
立即將它插入 Lambda 運算式中。 例如:
auto Sqr = [](int t) __declspec(code_seg("PagedMem")) -> int { return t*t; };
若要判斷 Lambda 是否支援特定修飾詞,請參閱 Microsoft 特定修飾詞一節中的 修飾 詞一文。
Visual Studio 支援 C++11 標準 Lambda 功能,以及 無狀態 Lambda。 無狀態 Lambda 可轉換成使用任意呼叫慣例的函式指標。
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應