1. 程式人生 > >C++ function bind以及lamda表示式

C++ function bind以及lamda表示式

本文是C++0x系列的第四篇,主要是內容是C++0x中新增的lambda表示式, function物件和bind機制。之所以把這三塊放在一起講,是因為這三塊之間有著非常密切的關係,通過對比學習,加深對這部分內容的理解。在開始之間,首先要講一個概念,closure(閉包),這個概念是理解lambda的基礎。下面我們來看看wikipedia上對於計算機領域的closure的定義:

A closure (also lexical closure, function closure or function
value)isfunction together with a referencing environment for the non-local variables of that function.

上面的大義是說,closure是一個函式和它所引用的非本地變數的上下文環境的集合。從定義我們可以得知,closure可以訪問在它定義範圍之外的變數,也即上面提到的non-local vriables,這就大大增加了它的功力。關於closure的最重要的應用就是回撥函式,這也是為什麼這裡把function, bind和lambda放在一起講的主要原因,它們三者在使用回撥函式的過程中各顯神通。下面就為大家一步步接開這三者的神祕面紗。

1. function

我們知道,在C++中,可呼叫實體主要包括函式,函式指標,函式引用,可以隱式轉換為函式指定的物件,或者實現了opetator()的物件(即C++98中的functor)。C++0x中,新增加了一個std::function物件,std::function物件是對C++中現有的可呼叫實體的一種型別安全的包裹(我們知道像函式指標這類可呼叫實體,是型別不安全的)。我們來看幾個關於function物件的例子:

#include < functional>
 
std::function< size_t (const char*) > print_func;
 
/// normal function -> std::function object
size_t CPrint(const char*) { ... } print_func = CPrint; print_func("hello world"): /// functor -> std::function object class CxxPrint { public: size_t operator()(const char*) { ... } }; CxxPrint p; print_func = p; print_func("hello world");

    在上面的例子中,我們把一個普通的函式和一個functor賦值給了一個std::function物件,然後我們通過該物件來呼叫。其它的C++中的可呼叫實體都可以像上面一樣來使用。通過std::function的包裹,我們可以像傳遞普通的物件一樣來傳遞可呼叫實體,這樣就很好解決了型別安全的問題。瞭解了std::function的基本用法,下面我們來看一些使用過程中的注意事項:

  • (1)關於可呼叫實體轉換為std::function物件需要遵守以下兩條原則:
    a. 轉換後的std::function物件的引數能轉換為可呼叫實體的引數
    b. 可高用實體的返回值能轉換為std::function物件的(這裡注意,所有的可呼叫實體的返回值都與返回void的std::function物件的返回值相容)。
  • (2)std::function物件可以refer to滿足(1)中條件的任意可呼叫實體
  • (3)std::function object最大的用處就是在實現函式回撥,使用者需要注意,它不能被用來檢查相等或者不相等
2. bind

bind是這樣一種機制,它可以預先把指定可呼叫實體的某些引數繫結到已有的變數,產生一個新的可呼叫實體,這種機制在回撥函式的使用過程中也頗為有用。C++98中,有兩個函式bind1st和bind2nd,它們分別可以用來繫結functor的第一個和第二個引數,它們都是隻可以繫結一個引數。各種限制,使得bind1st和bind2nd的可用性大大降低。C++0x中,提供了std::bind,它繫結的引數的個數不受限制,繫結的具體哪些引數也不受限制,由使用者指定,這個bind才是真正意義上的繫結,有了它,bind1st和bind2nd就沒啥用武之地了,因此C++0x中不推薦使用bind1st和bind2nd了,都是deprecated了。下面我們通過例子,來看看bind的用法:

#include < functional>
 
int Func(int x, int y);
auto bf1 = std::bind(Func, 10, std::placeholders::_1);
bf1(20); ///< same as Func(10, 20)
 
class A
{
public:
    int Func(int x, int y);
};
 
A a;
auto bf2 = std::bind(&A::Func, a, std::placeholders::_1, std::placeholders::_2);
bf2(10, 20); ///< same as a.Func(10, 20)
 
std::function< int(int)> bf3 = std::bind(&A::Func, a, std::placeholders::_1, 100);
bf3(10); ///< same as a.Func(10, 100)

    上面的例子中,bf1是把一個兩個引數普通函式的第一個引數繫結為10,生成了一個新的一個引數的可呼叫實體體; bf2是把一個類成員函式綁定了類物件,生成了一個像普通函式一樣的新的可呼叫實體; bf3是把類成員函式綁定了類物件和第二個引數,生成了一個新的std::function物件。看懂了上面的例子,下面我們來說說使用bind需要注意的一些事項:

  • (1)bind預先繫結的引數需要傳具體的變數或值進去,對於預先繫結的引數,是pass-by-value的
  • (2)對於不事先繫結的引數,需要傳std::placeholders進去,從_1開始,依次遞增。placeholder是pass-by-reference的
  • (3)bind的返回值是可呼叫實體,可以直接賦給std::function物件
  • (4)對於繫結的指標、引用型別的引數,使用者需要保證在可呼叫實體呼叫之前,這些引數是可用的
  • (5)類的this可以通過物件或者指標來繫結
3. lambda

講完了function和bind, 下面我們來看lambda。有python基礎的朋友,相信對於lambda不會陌生。看到這裡的朋友,請再回憶一下前面講的closure的概念,lambda就是用來實現closure的東東。它的最大用途也是在回撥函式,它和前面講的function和bind有著千絲萬縷的關係。下面我們先通過例子來看看lambda的廬山真面目:

vector< int> vec;
/// 1. simple lambda
auto it = std::find_if(vec.begin(), vec.end(), [](int i) { return i > 50; });
class A
{
public:
    bool operator(int i) const { return i > 50; }
};
auto it = std::find_if(vec.begin(), vec.end(), A());
 
/// 2. lambda return syntax
std::function< int(int)> square = [](int i) -> int { return i * i; }
 
/// 3. lambda expr: capture of local variable
{
    int min_val = 10;
    int max_val = 1000;
 
    auto it = std::find_if(vec.begin(), vec.end(), [=](int i) {
        return i > min_val && i < max_val; 
        });
 
    auto it = std::find_if(vec.begin(), vec.end(), [&](int i) {
        return i > min_val && i < max_val;
        });
 
    auto it = std::find_if(vec.begin(), vec.end(), [=, &max_value](int i) {
        return i > min_val && i < max_val;
        });
}
 
/// 4. lambda expr: capture of class member
class A
{
public:
    void DoSomething();
 
private:
    std::vector<int>  m_vec;
    int               m_min_val;
    int               m_max_va;
};
 
/// 4.1 capture member by this
void A::DoSomething()
{
    auto it = std::find_if(m_vec.begin(), m_vec.end(), [this](int i){
        return i > m_min_val && i < m_max_val; });
}
 
/// 4.2 capture member by default pass-by-value
void A::DoSomething()
{
    auto it = std::find_if(m_vec.begin(), m_vec.end(), [=](int i){
        return i > m_min_val && i < m_max_val; });
}
 
/// 4.3 capture member by default pass-by-reference
void A::DoSomething()
{
    auto it = std::find_if(m_vec.begin(), m_vec.end(), [&](int i){
        return i > m_min_val && i < m_max_val; });
}

上面的例子基本覆蓋到了lambda表達的基本用法。我們一個個來分析每個例子(標號與上面程式碼註釋中1,2,3,4一致):

  • (1)這是最簡單的lambda表示式,可以認為用了lambda表示式的find_if和下面使用了functor的find_if是等價的
  • (2)這個是有返回值的lambda表示式,返回值的語法如上面所示,通過->寫在引數列表的括號後面。返回值在下面的情況下是可以省略的:
    a. 返回值是void的時候
    b. lambda表示式的body中有return expr,且expr的型別與返回值的一樣
  • (3)這個是lambda表示式capture本地區域性變數的例子,這裡三個小例子,分別是capture時不同的語法,第一個小例子中=表示capture的變數pass-by-value, 第二個小拿出中&表示capture的變數pass-by-reference,第三個小例子是說指定了default的pass-by-value, 但是max_value這個單獨pass-by-reference
  • (4)這個是lambda表示式capture類成員變數的例子,這裡也有三個小例子。第一個小例子是通過this指標來capture成員變數,第二、三個是通過預設的方式,只不過第二個是通過pass-by-value的方式,第三個是通過pass-by-reference的

分析完了上面的例子,我們來總結一下關於lambda表示式使用時的一些注意事項:

  • (1)lambda表示式要使用引用變數,需要遵守下面的原則:
    a. 在呼叫上下文中的區域性變數,只有capture了才可以引用(如上面的例子3所示)
    b. 非本地區域性變數可以直接引用
  • (2)使用者需要注意,closure(lambda表示式生成的可呼叫實體)引用的變數(主要是指標和引用),在closure呼叫完成之前,必須保證可用,這一點和上面bind繫結引數之後生成的可呼叫實體是一致的
  • (3)關於lambda的用處,就是用來生成closure,而closure也是一種可呼叫實體,所以可以通過std::function物件來儲存生成的closure,也可以直接用auto

通過上面的介紹,我們基本瞭解了function, bind和lambda的用法,把三者結合起來,C++將會變得非常強大,有點函數語言程式設計的味道了。最後,這裡再補充一點,對於用bind來生成function和用lambda表示式來生成function, 通常情況下兩種都是ok的,但是在引數多的時候,bind要傳入很多的std::placeholders,而且看著沒有lambda表示式直觀,所以通常建議優先考慮使用lambda表示式。

http://blog.csdn.net/hongjunbj/article/details/8891387

再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!希望你也加入到我們人工智慧的隊伍中來!https://www.cnblogs.com/captainbed