學習C++11的一些思考和心得(1):lambda,function,bind和委托


 1.lambda表達式

lanbda表達式簡單地來講就是一個匿名函數,就是沒有名稱的函數,如果以前有接觸過python或者erlang的人都比較熟悉這個,這個可以很方便地和STL里面的算法配合

    std::for_each(list.begin(),list.end(),
                  [ &tmp,id ](struct ComingVesselInfo *info)
                  {
                    if( info->m_nShipID == id)
                    {
                        tmp = info;
                        return ;
                    }
                  }
                 );

這個是我在項目里面使用的一段代碼,如果沒有lambda,的代碼則是這樣的:

 

    for ( auto iter = list.begin();
           iter != list.end();
           ++iter)
    {
        if ( iter->m_nShipID == id)
        {
            tmp = iter;
        }
    }

從上面的代碼比較並沒有看出lambda有什么用,反而喪失了一點代碼的可讀性,但是我覺得lambda的引入不是我們可以在函數內直接很方便地寫函數,而是可以很好地跟STL algorithm庫配合,在我們的項目中,我很少使用到STL algorithm庫,因為大部分算法函數都有下面的兩個問題:

1.無法獲取返回值

2.無法傳入其他的參數(比如上面的for_each的例子,如果沒有lambda,並沒有辦法把tmp和id傳入)

lambda的引入解決了我在項目中使用STL的algorithm的顧慮

順便說下,上面的auto也是C++11的新標准,就是自動推導右邊數據的類型

 

2.function和bind以及委托


在C#里面,我們經常會看到使用委托的,C#的委托語法是這樣的:

public delegate void Ticket();

這樣的話,我們只要聲明一個Ticket t,這個只要符合functionname()這個形式的參數,不管是static 函數還是類成員函數,都可以賦值給t,並且可以通過t()來調用該函數或者成員函數

 

很多學C#的程序員一看這個就簡單地把委托認為是C++里面的函數指針,其實這個是錯的,委托和函數指針的區別在於,委托是有狀態的,更類似與閉包,而函數指針只是一個地址,是沒有狀態的,單純的函數指針無法根據我們的需求去傳入外部的狀態(也就是無法根據需求去改變傳入參數的個數和參數的類型)

在C++里面,我們只能通過這樣來指向一個成員函數的指針:

int (simple:: *pfn) (int)  = simple::fn;

這樣地話我們才能把一個類成員函數指針指向一個類的成員函數,但是這樣明顯有兩個缺點:

1.我們需要在函數調用的時候才能傳入參數,無法在函數確定要指向哪一個類成員函數的時候指定參數

2.最重要的一點,我們無法把pfn認識指向一個functionname(int)的類成員函數,我們只能再次定義一個函數指針

 

在C++里面實際上也是有類似於委托的功能,利用類的成員變量來保存我們的狀態,然后重載operator(),就可以達到我們所說的閉包的效果

 

class Add
{
public:
    Add(int i,int j)
    {
        m_i = i;
        m_j = j;
    }

    int operator()()
    {
        return m_i + m_j;
    }

protected:
    int m_i;
    int m_j;
};

這樣我們就可以這樣使用這個Add類

    Add add(1,2);
    cout<<add()<<endl;

但是很明顯地,這樣做我們每次都需要根據需求去創建一個class,實際上並沒有比我們根據需求來寫函數方便地多少,還不如直接使用lambda,在C++ 11則解決了這個問題,引入了boost的function和bind

class calc
{
public:
    calc(int base)
    {
        m_base = base;
    }

    int Add(int j)
    {
        m_base += j;
        return m_base;
    }

    int sub(int j)
    {
        m_base -= j;
        return m_base;
    }
protected:
    int m_base;
};
int main(int argc,char *argv[])
{
    calc *c = new calc(10);
    tr1::function<int ()> fun;
    
    fun = tr1::bind(&calc::Add,c,3);
    cout<<fun()<<endl;

    tr1::function<int (int)> fun2;
    fun2 = tr1::bind(&calc::sub,c,tr1::_1);
    cout<<fun2(3);

}

function和bind的引入解決了我們無法使用C#里面委托的問題,並且既可以提前綁定參數,又可以延后綁定參數,而且可以跟任何類成員函數配合

看起來是很方便的,但是別忘記了C++里面的一個問題,就是C++沒有內存回收的功能,如果我們bind的類被釋放掉了,那么會怎么樣??

如果我們main函數改成下面這樣調用:

    calc *c = new calc(10);
    tr1::function<int ()> fun;
    
    
    fun = tr1::bind(&calc::Add,c,3);
    delete c;
    c = NULL;

    cout<<fun()<<endl;

那么我們調用fun()的時候會發生什么事情??

我們無法得知計算得結果,因為此時的c已經被delete掉了,雖然可以依然正常調用Add函數(如果對C++比較熟悉的應該知道為什么可以正常調用Add),但是內部我們保存的"狀態"不見了,也隨着delete動作一起消失了,留下的是一串隨機的數

但是如果我們把下面的代碼改成這樣:

tr1::function<int ()> fun2;
{
  calc d(12);
  fun2 = tr1::bind(&calc::Add,d,40);
}

fun2();

那么即使在d釋放掉以后,我們依然可以正常地調用fun2

根據我的測試,在我們調用bind的時候,實際上將整個類的當前"狀態"都復制了過去,而且在VS2010的平台上,我自己測試了下,調用了9次的復制構造函數,如果我們不好好處理這個,對於性能將會是巨大的損失

當時標准庫的設計者也沒有那么地傻,所以bind也可以傳入指針:

fun2 = tr1::bind(&calc::Add,&d,40);

當然這個又涉及到類的狀態保存的問題,類銷毀了怎么辦??

所以說C++的function和bind的再能模仿,也無法模仿到C#的委托,畢竟C++里面沒有人幫我們管理內存,而C#程序員似乎不用關心這些,C++程序員永遠離不開內存管理的話題

 

參考資料:

以boost::function和boost:bind取代虛函數

http://www.boost.org/doc/libs/1_54_0/doc/html/function.html

http://www.boost.org/doc/libs/1_54_0/libs/bind/bind.html

http://msdn.microsoft.com/zh-cn/library/vstudio/dd293608.aspx

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM