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
