「C++11」Lambda 表示式

kedebug發表於2013-07-30

 a lambda維基百科上面對於 lambda 的引入是如下描述的:

在標準 C++,特別是當使用 C++ 標準程式庫演算法函式諸如 sort 和 find。使用者經常希望能夠在演算法函式呼叫的附近定義一個臨時的述部函式(又稱謂詞函式,predicate function)。由於語言本身允許在函式內部定義型別,可以考慮使用函式物件,然而這通常既麻煩又冗贅,也阻礙了程式碼的流程。此外,標準 C++ 不允許定義於函式內部的型別被用於模板,所以前述的作法是不可行的。C++11 對lambda的支援可以解決上述問題。

lambda 表示式的簡單語法如下:[capture] (parameters) -> return value { body }

1、最簡單的例子:

#include <iostream>
using namespace std;
int main()
{
    auto func = [] () { cout << "Hello world"; };
    func(); 
}

上面的 lambda 表示式 func 沒有傳入任何引數,也沒有返回值,甚至我們可以對其簡寫成:auto func = [] { cout << "Hello world"; } 。並且配合 C++11標準加入的 auto 自動型別判斷,省去了以前定義函式指標冗雜繁瑣的過程,程式看上去如何優雅、簡潔。

2、更加深入的示範:

假設我們有一個存放書籍地址的類,需要傳入一個“搜尋滿足條件地址”的函式,並且將類定義成如下模樣:

class AddressBook
{
public:
    template<typename Func>
    std::vector<std::string> findMatchingAddresses (Func func)
    { 
        std::vector<std::string> results;
        for ( auto itr = _addresses.begin(), end = _addresses.end(); itr != end; ++itr )
        {    
            if ( func( *itr ) )
            {
                results.push_back( *itr );
            }
        }
        return results;
    }
private:
    std::vector<std::string> _addresses;
};

類 AddressBook 封裝了 findMatchingAddresses 函式,返回滿足我們需要的書目,下面我們看看 lambda 表示式如何實現這一過程:

AddressBook global_address_book;
vector<string> findAddressesFromOrgs ()
{
    return global_address_book.findMatchingAddresses( 
        [] (const string& addr) { return addr.find( ".org" ) != string::npos; } 
    );
}

上面函式返回滿足地址中帶有 ".org" 字樣的書籍條目,lambda 表示式雖然沒有定義返回型別,但是編譯器可以根據我們的 return 語句自動判斷返回值是 boolean 型別。我們的 lambda 表示式中 [] 並沒有 capture 任何變數,再下面的例子中將展示 [&] :

string name;
cin >> name;
return global_address_book.findMatchingAddresses( 
    [&] (const string& addr) { return addr.find( name ) != string::npos; } 
);

再次注意到,類 global_address_book 竟然能夠訪問到我們定義的區域性變數 name 字串,這正是 lambda 表示式的強大之處,[&] 代表 lambda body 中用到的變數都以“reference”的方式使用,還有更多的 capture 用法這裡就不再敘述,有興趣進一步瞭解的同學可以自行搜尋。

3、Lambda 表示式使 STL 更加強大:

傳統的情況下,我們會用下面的方式去訪問容器裡面的資料:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
    cout << *itr;
}

但是當我們有了 Lambda 之後,利用 STL 裡面的 for_each ,將會變成下面的程式碼:

vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for_each( v.begin(), v.end(), [] (int val)
{
    cout << val;
});

你可能會想,上面的 for_each 迴圈,會不會使我們的程式有效能上的損耗?答案是否定的:for_each 的效率和迭代的效率是一致的,甚至加上 Lambda 之後,for_each 會利用 "loop unrolling" 機制使程式執行的更快。

Lambda 的引入給我們帶來了一種全新的程式設計體驗,它可以讓我們把 "function" 當做是 "data" 一樣傳遞,並且使我們從繁瑣的語法中解放出來,更加關注於 "演算法" 本身。我們也稱 Lambda 為 Closure(閉包),顧名思義,這使我們的函式變得更加私有,所以限制了別人的訪問,同時我們也可以更加方便的程式設計。

4、Lambda 與 資源管理:

前面在我的 「理解智慧指標」一文中提到,智慧指標可以利用 C++ 的 RAII(Resource acquisition is initialization) 特性,在型別(class)的解構函式時來完成自動釋放指標所指向物件的目的。同樣,在 Lambda 中,又把 RAII 這一特性體現的淋漓盡致:

class ScopeGuard
{
public:
    explicit ScopeGuard(std::function<void()> onExitScope)
        : onExitScope_(onExitScope)
    { }
    ~ScopeGuard()
    {
            onExitScope_();
    }
private:
    std::function<void()> onExitScope_;
private: // noncopyable
    ScopeGuard(ScopeGuard const&);
    ScopeGuard& operator=(ScopeGuard const&);
};
int main() {
    HANDLE h = CreateFile(...);
    ScopeGuard onExit([&] { CloseHandle(h); });
    ...
return 0;
}

看到上面的程式碼,我已經被 C++11 引入 Lambda 之後所帶來的強大功能所折服了。我們不必擔心何時去釋放資源,並且連釋放資源的方式「如 CloseHandle(h)」也與我們的程式碼緊密的融合在了一起,這將是十分美妙的一件事情。

5、Lambda 到底是什麼型別:

auto func = [] () { cout << "hello world"; };
std::function<void ()> func = [] () { cout << "hello world"; };

auto func = [] (int val) { cout << val; return false; };
std::function<bool (int)> func = [] (int val) { cout << val; return false; };

 上面的上下 2 行程式碼效果是等效的,看到這裡是否有種似曾相識的感覺?那 Lambda 和 我們定義的函式指標有什麼區別呢:

typedef int (*func)();
func f = [] () -> int { return 2; };
f();

沒錯,這段程式碼是可以正常執行的,因為 Lambda 表示式中並沒有 capture 任何本地變數,因此會被編譯成普通的函式指標。最後採用 coolshell 裡面 Lambda 的 2 點總結: 1)可以定義匿名函式,2)編譯器會把其轉成函式物件。 

6、 使用 Lambda 進行程式碼委託(呼叫):

 

「參考資料」

http://zh.wikipedia.org/wiki/C%2B%2B11

http://mindhacks.cn/2012/08/27/modern-cpp-practices/

http://www.cprogramming.com/c++11/c++11-lambda-closures.html

http://coolshell.cn/articles/5265.html

相關文章