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

鏈接 | https://hackernoon.com/all-about-Lambda-functions-in-cfrom-c11-to-c17-2t1j32qw

翻譯 | CSDN

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

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

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

什麼是 Lambda 函數?

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

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

也不能重複使用。

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

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

{  method definition
}

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

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

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

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

struct print
{
    void operator()(int element)
    {
        cout << element << endl;
    }
};
int main(void)
{
    std::vector<int> 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; });

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() 函數的參數。

使用 Lambda 函數的好處

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

此外,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

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

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

Lambda 函數的捕獲列表

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

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

template <typename Functor>
void f(Functor functor)
{
    std::cout << __PRETTY_FUNCTION__ << std::endl;
}
/* Or alternatively you can use this
void f(std::function<int(int)> 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(int)>]
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++ 的 Lambda 函數類型

泛型 Lambda,代碼如下:

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


// is equivalent to
struct anonymous
{
    template <class T0, class T1, class T2>
    auto operator()(T0 a, T1 b, T2 c) const
    {
    }
};

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

可變泛型 Lambda,代碼如下:

void print() {}
template <typename First, typename... Rest>
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_traits>


int main()
{
    auto funcPtr = +[] {};
    static_assert(std::is_same<decltype(funcPtr), void (*)()>::value);
}

你可以強制編譯器生成 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 函數。這將爲代碼的定製、代碼表示性和緊湊性(順便說一句,沒有這樣的詞)打開無限可能的大門。

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 T1, class T2>
        constexpr auto operator()(T1 a, T2 b) const
        {
            return a + b;
        }
    };
*/
constexpr int answer = sum(10, 10);

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

結束語

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

關注公衆**「高效程序員」**👇,一起優秀!

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/5yWbbGmmKvlvEkuAURpsqw