11.30 貫穿 C++ 11 與 C++ 17 的 Lambda 到底是個什麼?

本文將詳解Lambda函數從定義到學習和使用,涉及一些不為人知的事情,如LIFE-立即調用的函數表達式,Lambda的類型。相信你已經起了興趣,那就開始閱讀吧。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

作者 | Vishal Chovatiya

譯者 | 蘇本如,責編 | maozz

出品 | CSDN(ID:CSDNnews)

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

Lambda函數是C++ 11中引入的現代C++的一個直觀概念,因此在互聯網上可以找到大量的關於Lambda函數的文章。但是仍然有一些不為人知的事情(如LIFE-立即調用的函數表達式,Lambda的類型等等)鮮有人談論。因此,在這篇文章裡,我不僅要向你展示C++中的Lambda函數,同時還要介紹它的內部工作機制,以及Lambda函數的其他方方面面。

這篇文章的標題有點誤導人。因為Lambda並不總是轉化為函數指針。實際上它是一個表達式(確切地說是唯一的閉包)。為了簡單起見,在這篇文章中,我會一直互換使用Lambda函數和Lambda表達式。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

什麼是Lambda函數?

Lambda函數是簡短的代碼片段,它:

不值得命名(匿名的、未被命名的、一次性的,等等,無論你怎麼稱呼它),

也不能重複使用。

換句話說,它只是一種糖衣語法(syntactic sugar)。Lambda函數的語法定義如下:

[ capture list ] (parameters) -> return-type

{ method definition

}

編譯器通常會計算Lambda函數本身的返回類型。因此,我們不需要顯式地給它指定一個尾置返回類型,如-> return-type。

但在一些複雜的情況下,編譯器無法推斷返回類型,這時候我們就需要給它指定一個返回類型。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

為什麼我們要使用Lambda函數?

C++包含許多有用的通用函數,如std::for_each,它們可以很方便。不幸的是,它們的使用有時也很麻煩,特別是如果你想應用的函子是特定函數的唯一函子的話。以下面的代碼為例:

struct print

{

void operator(int element)

{

cout << element << endl;

}

};

int main(void)

{

std::vector v = {1, 2, 3, 4, 5};

std::for_each(v.begin, v.end, print);

return 0;

}

如果你只是在特定的地方使用一次print,那麼僅僅為了做一些瑣碎的和一次性的事情而編寫一個完整的類,就顯得有些過猶不及了。

對於上面的這種情形,使用內聯代碼會更合適,這可以通過Lambda函數來實現,如下所示:

std:for_each(v.begin, v.end, (int element) { cout << element << endl; });

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

Lambda函數內部是如何工作的?

[&i] { std::cout << i; }

// is equivalent to

struct anonymous

{

int &m_i;

anonymous(int &i) : m_i(i) {}

inline auto operator const

{

std::cout << i;

}

};

編譯器為每個Lambda函數生成如上所述的唯一閉包。注意,這是Lambda函數的核心所在。

捕獲列表將成為閉包中的構造函數的參數,如果將參數按值捕獲,那麼相應類型的數據成員將在閉包中創建。

此外,可以在Lambda函數的參數中聲明變量/對象,它們將成為調用operator函數的參數。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

使用Lambda函數的好處

零成本抽象。對!你沒有看錯。Lambda函數不會降低性能,它的性能和普通的函數一樣好。

此外,Lambda函數使代碼變得更加緊湊、更加結構化和更富有表現力。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

學習Lambda表達式

按引用/值來捕獲,代碼如下:

int main

{

int x = 100, y = 200;

auto print = [&] { // Capturing object by reference

std::cout << __PRETTY_FUNCTION__ << " : " << x << " , " << y << std::endl;

};

print;

return 0;

}

上面代碼的輸出如下:

main::<lambda> : 100 , 200/<lambda>

在上面的例子中,我在捕獲列表中對“&”符號作了註釋。它表示按引用來捕獲變量x和y。類似地,“=”符號表示按值捕獲,它將在閉包中創建相同類型的數據成員,並且將執行copy-assignment操作。

請注意,參數列表是可選的,如果你不向Lambda表達式傳遞任何參數,則可以省略空括號。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

Lambda函數的捕獲列表

下表顯示了捕獲列表中不同用法的含義:

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

將Lambda函數作為參數傳遞,代碼如下:

template <typename>

void f(Functor functor)

{

std::cout << __PRETTY_FUNCTION__ << std::endl;

}

/* Or alternatively you can use this

void f(std::function functor)

{

std::cout << __PRETTY_FUNCTION__ << std::endl;

}

*/

int g { static int i = 0; return i++; }

int main

{

auto Lambda_func = [i = 0] mutable { return i++; };

f(Lambda_func); // Pass Lambda

f(g); // Pass function

}

上面代碼的輸出如下:

Function Type : void f(Functor) [with Functor = main()::<lambda>]/<lambda>

Function Type : void f(Functor) [with Functor = int (*)(int)]

你還可以將Lambda函數作為參數傳遞給其他函數,就像我在上面編寫的普通函數一樣。

如果你注意到了,這裡我在捕獲列表中聲明瞭變量i,它將成為數據成員。因此,每次調用Lambda_func時,它都將返回並遞增。

捕獲Lambda函數中的成員變量或this指針,代碼如下:

class Example

{

public:

Example : m_var(10) {}

void func

{

[=] { std::cout << m_var << std::endl; }; // IIFE

}

private:

int m_var;

};

int main

{

Example e;

e.func;

}

也可以使用[this], [=] 或者 [&]來捕獲This指針。在任何這些情況下,類中的數據成員(包括private類型的數據成員)都可以像在普通方法中那樣被訪問。

如果你看到Lambda表達式行,我在Lambda函數聲明的末尾使用了額外的 ,這個額外的 用來表示在聲明之後立即調用它,它被稱為IIFE(立即調用的函數表達式)。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

C++的Lambda函數類型

泛型Lambda,代碼如下:

const auto l = (auto a, auto b, auto c) {};

// is equivalent to

struct anonymous

{

template <class>

auto operator(T0 a, T1 b, T2 c) const

{

}

};

在C++ 14中引入的泛型Lambda,它可以使用auto標識符捕獲參數。

可變泛型Lambda,代碼如下:

void print {}

template <typename>

void print(const First &first, Rest &&... args)

{

std::cout << first << std::endl;

print(args...);

}

int main

{

auto variadic_generic_Lambda = (auto... param) {

print(param...);

};

variadic_generic_Lambda(1, "lol", 1.1);

}

帶可變參數包的Lambda在許多情況下都很有用,如代碼調試、不同數據輸入的重複操作等。

可變(Mutable)Lambda函數

通常,Lambda函數的call-operator(調用運算符)隱式為const-by-value(常量,按值捕獲),這意味著它是不可變的。如果要按值捕獲任何內容,需要在Lambda函數體前使用mutable關鍵字。代碼如下所示:

mutable {}

// is equivalent to

struct anonymous

{

auto operator // call operator

{

}

};

我們已經在上面看到了上述情況的一個例子。希望你注意到了。

Lambda作為函數指針,代碼如下:

#include <iostream>

#include <type>

int main

{

auto funcPtr = + {};

static_assert(std::is_same<decltype>::value);/<decltype>

}

你可以強制編譯器生成Lambda作為函數指針,而不是像上面那樣在它前面添加+來使之成為閉包。

高階函數:Lambda可以作為參數和返回值,代碼如下:

const auto less_than = (auto x) {

return [x](auto y) {

return y < x;

};

};

int main(void)

{

auto less_than_five = less_than(5);

std::cout << less_than_five(3) << std::endl;

std::cout << less_than_five(10) << std::endl;

return 0;

}

再進一步,Lambda函數還可以返回另一個Lambda函數。這將為代碼的定製、代碼表示性和緊湊性(順便說一句,沒有這樣的詞)打開無限可能的大門。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

constexpr Lambda表達式

從C++ 17開始,Lambda表達式可以被聲明為constexpr(常量表達式)。

constexpr auto sum = (const auto &a, const auto &b) { return a + b; };

/*

is equivalent to

constexpr struct anonymous

{

template <class>

constexpr auto operator(T1 a, T2 b) const

{

return a + b;

}

};

*/

constexpr int answer = sum(10, 10);

即使你沒有指定constexpr關鍵字,如果函數調用運算符恰好滿足所有constexpr函數的要求,那麼它也將是constexpr。

贯穿 C++ 11 与 C++ 17 的 Lambda 到底是个什么?

結束語

希望你喜歡這篇文章。我試著用幾個簡單的小例子來涵蓋使用Lambda的大多數複雜情況。考慮到代碼的表達性和易維護性,你應該在滿足Lambda使用條件的所有地方都優先使用它,就像你可以將其和大多數STL算法一起用於智能指針的自定義刪除器中一樣。

原文:https://hackernoon.com/all-about-Lambda-functions-in-cfrom-c11-to-c17-2t1j32qw

本文為 CSDN 翻譯,轉載請註明來源出處。


分享到:


相關文章: