C++11 std::function, std::bind, std::ref, std::cref
轉自:http://www.jellythink.com/
std::function
看看這段代碼
先來看看下面這兩行代碼:
std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
std::function<void(EventKeyboard::KeyCode, Event*)> onKeyReleased;
這兩行代碼是從Cocos2d-x中摘出來的,重點是這兩行代碼的定義啊。std::function
這是什么東西?如果你對上述兩行代碼表示毫無壓力,那就不妨再看看本文,就當溫故而知新吧。
std::function介紹
類模版std::function
是一種通用、多態的函數封裝。std::function
的實例可以對任何可以調用的目標實體進行存儲、復制、和調用操作,這些目標實體包括普通函數、Lambda表達式、函數指針、以及其它函數對象等。std::function
對象是對C++中現有的可調用實體的一種類型安全的包裹(我們知道像函數指針這類可調用實體,是類型不安全的)。
通常std::function是一個函數對象類,它包裝其它任意的函數對象,被包裝的函數對象具有類型為T1, …,TN的N個參數,并且返回一個可轉換到R類型的值。std::function
使用 模板轉換構造函數接收被包裝的函數對象;特別是,閉包類型可以隱式地轉換為std::function
。
最簡單的理解就是:
通過
std::function
對C++中各種可調用實體(普通函數、Lambda表達式、函數指針、以及其它函數對象等)的封裝,形成一個新的可調用的std::function
對象;讓我們不再糾結那么多的可調用實體。一切變的簡單粗暴。
怎么使用std::function
使用std::function
的感覺就是“萬眾歸一”,下面就通過實際的代碼例子,看看究竟怎么使用std::function
。會使用了才是王道。
#include <functional>
#include <iostream>
using namespace std;std::function< int(int)> Functional;// 普通函數
int TestFunc(int a)
{return a;
}// Lambda表達式
auto lambda = [](int a)->int{ return a; };// 仿函數(functor)
class Functor
{
public:int operator()(int a){return a;}
};// 1.類成員函數
// 2.類靜態函數
class TestClass
{
public:int ClassMember(int a) { return a; }static int StaticMember(int a) { return a; }
};int main()
{// 普通函數Functional = TestFunc;int result = Functional(10);cout << "普通函數:"<< result << endl;// Lambda表達式Functional = lambda;result = Functional(20);cout << "Lambda表達式:"<< result << endl;// 仿函數Functor testFunctor;Functional = testFunctor;result = Functional(30);cout << "仿函數:"<< result << endl;// 類成員函數TestClass testObj;Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);result = Functional(40);cout << "類成員函數:"<< result << endl;// 類靜態函數Functional = TestClass::StaticMember;result = Functional(50);cout << "類靜態函數:"<< result << endl;return 0;
}
對于各個可調用實體轉換成std::function
類型的對象,上面的代碼都有,運行一下代碼,閱讀一下上面那段簡單的代碼。總結了簡單的用法以后,來看看一些需要注意的事項:
-
關于可調用實體轉換為
std::function
對象需要遵守以下兩條原則:
- 轉換后的
std::function
對象的參數能轉換為可調用實體的參數; - 可調用實體的返回值能轉換為
std::function
對象的返回值。
- 轉換后的
-
std::function
對象最大的用處就是在實現函數回調,使用者需要注意,它不能被用來檢查相等或者不相等,但是可以與NULL或者nullptr進行比較。
為什么要用std::function
好用并實用的東西才會加入標準的。因為好用,實用,我們才在項目中使用它。std::function
實現了一套類型消除機制,可以統一處理不同的函數對象類型。以前我們使用函數指針來完成這些;現在我們可以使用更安全的std::function
來完成這些任務。
還有為什么?我也不知道還有為什么?等以后發現了更好的實際應用實例再回來說為什么吧。
std::bind
看看這段代碼
這幾天學習Cocos2d-x,看到了以下的一段代碼:
// new callbacks based on C++11
#define CC_CALLBACK_0(__selector__,__target__, ...) std::bind(&__selector__,__target__, ##__VA_ARGS__)#define CC_CALLBACK_1(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, ##__VA_ARGS__)#define CC_CALLBACK_2(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, ##__VA_ARGS__)#define CC_CALLBACK_3(__selector__,__target__, ...) std::bind(&__selector__,__target__, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, ##__VA_ARGS__)
都是定義的一些宏,如果你看了上面的這段代碼,覺的很簡單,那么這篇文章你完全可以pass了;如果你對上面定義的這些宏,完全不知道是什么意思,那么,這篇文章就屬于你,完全屬于你的菜。
通過這篇文章,我將帶你進入C++11中std::bind的世界,讓我們起航吧。
先來看看std::bind1st和std::bind2nd
bind是這樣一種機制,它可以預先把指定可調用實體的某些參數綁定到已有的變量,產生一個新的可調 用實體,這種機制在回調函數的使用過程中也頗為有用。C++98中,有兩個函數bind1st和bind2nd,它們分別可以用來綁定functor的第 一個和第二個參數,它們都是只可以綁定一個參數。各種限制,使得bind1st和bind2nd的可用性大大降低。
如果通過上面的內容,你還沒有明白std::bind1st和std::bind2nd到底是何方圣神到底是什么東西,那就看這段代碼示例:
#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;int main()
{vector<int> coll;for (int i = 1; i <= 10; ++i){coll.push_back(i);}// 查找元素值大于10的元素的個數// 也就是使得10 < elem成立的元素個數 int res = count_if(coll.begin(), coll.end(), bind1st(less<int>(), 10));cout << res << endl;// 查找元素值小于10的元素的個數// 也就是使得elem < 10成立的元素個數 res = count_if(coll.begin(), coll.end(), bind2nd(less<int>(), 10));cout << res << endl;return 0;
}
通過上面的代碼明白了std::bind1st和std::bind2nd了么?還沒有明白?好吧,我接著往細了講。
對于上面的代碼,less<int>()
其實是一個仿函數,如果沒有std::bind1st和std::bind2nd,那么我們可以這樣使用less<int>()
,代碼如下:
less<int> functor = less<int>();
bool bRet = functor(10, 20); // 返回true
看到了么?less<int>()
這個仿函數對象是需要兩個參數的,比如10<20進行比較,那么10叫做left參數,20叫做right參數。
- 當使用
std::bind1st
的時候,就表示綁定了left參數,也就是left參數不變了,而right參數就是對應容器中的element; - 當使用
std::bind2nd
的時候,就表示綁定了right參數,也就是right參數不變了,而left參數就是對應容器中的element。
這下應該講明白了。
再來看看std::bind
C++11中提供了std::bind
。bind()函數的意義就像它的函數名一樣,是用來綁定函數調用的某些參數的。
bind的思想實際上是一種延遲計算的思想,將可調用對象保存起來,然后在需要的時候再調用。而且這種綁定是非常靈活的,不論是普通函數、函數對象、還是成員函數都可以綁定,而且其參數可以支持占位符,比如你可以這樣綁定一個二元函數auto f = bind(&func, _1, _2);
,調用的時候通過f(1,2)實現調用。
簡單的認為就是std::bind
就是std::bind1st
和std::bind2nd
的加強版。
怎么使用std::bind
一個知識點厲不厲害,歸根到底還是要經過實踐的考驗,下面就來看看std::bind
到底怎么用。
先看看《C++11中的std::function》中那段代碼,std::function
可以綁定全局函數,靜態函數,但是綁定類的成員函數時,必須要借助std::bind
的幫忙。但是話又說回來,不借助std::bind
也是可以完成的,只需要傳一個*this變量進去就好了,比如:
#include <iostream>
#include <functional>
using namespace std;class View
{
public:void onClick(int x, int y){cout << "X : " << x << ", Y : " << y << endl;}
};// 定義function類型, 三個參數
function<void(View, int, int)> clickCallback;int main(int argc, const char * argv[])
{View button;// 指向成員函數clickCallback = &View::onClick;// 進行調用clickCallback(button, 10, 123);return 0;
}
再來一段示例談談怎么使用std::bind代碼:
#include <iostream>
#include <functional>
using namespace std;int TestFunc(int a, char c, float f)
{cout << a << endl;cout << c << endl;cout << f << endl;return a;
}int main()
{auto bindFunc1 = bind(TestFunc, std::placeholders::_1, 'A', 100.1);bindFunc1(10);cout << "=================================\n";auto bindFunc2 = bind(TestFunc, std::placeholders::_2, std::placeholders::_1, 100.1);bindFunc2('B', 10);cout << "=================================\n";auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);bindFunc3(100.1, 30, 'C');return 0;
}
上面這段代碼主要說的是bind中std::placeholders的使用。 std::placeholders是一個占位符。當使用bind生成一個新的可調用對象時,std::placeholders表示新的可調用對象的第 幾個參數和原函數的第幾個參數進行匹配,這么說有點繞。比如:
auto bindFunc3 = bind(TestFunc, std::placeholders::_2, std::placeholders::_3, std::placeholders::_1);bindFunc3(100.1, 30, 'C');
可以看到,在bind的時候,第一個位置是TestFunc,除了這個,參數的第一個位置為占位符std::placeholders::_2,這就表示,調用bindFunc3的時候,它的第二個參數和TestFunc的第一個參數匹配,以此類推。
以下是使用std::bind的一些需要注意的地方:
- bind預先綁定的參數需要傳具體的變量或值進去,對于預先綁定的參數,是pass-by-value的;
- 對于不事先綁定的參數,需要傳std::placeholders進去,從_1開始,依次遞增。placeholder是pass-by-reference的;
- bind的返回值是可調用實體,可以直接賦給std::function對象;
- 對于綁定的指針、引用類型的參數,使用者需要保證在可調用實體調用之前,這些參數是可用的;
- 類的this可以通過對象或者指針來綁定。
為什么要使用std::bind
當我們厭倦了使用std::bind1st
和std::bind2nd
的時候,現在有了std::bind
,你完全可以放棄使用std::bind1st
和std::bind2nd
了。std::bind
綁定的參數的個數不受限制,綁定的具體哪些參數也不受限制,由用戶指定,這個bind才是真正意義上的綁定。
在Cocos2d-x中,我們可以看到,使用std::bind
生成一個可調用對象,這個對象可以直接賦值給std::function
對象;在類中有一個std::function
的變量,這個std::function
由std::bind
來賦值,而std::bind
綁定的可調用對象可以是Lambda表達式或者類成員函數等可調用對象,這個是Cocos2d-x中的一般用法。
以后遇到了“奇葩”用法再繼續總結了,一次也總結不完的。
又是一篇總結怎么使用的文章,如果你覺的看的不過癮,覺的我的文章寫的不痛不癢的,還想看點更深的東西,比如std::bind
是如何實現的啊?好吧,這篇文章確實沒有說這些深層次的東西,推薦這篇文章《bind原理圖釋》,希望這篇文章能滿足你哦。
std::ref, std::cref
C++本身有引用(&),為什么C++11又引入了std::ref(或者std::cref)?
主要是考慮函數式編程(**如std::bind、std::thread)**在使用時,是對參數直接拷貝,而不是引用。std::bind()是一個函數模板,它的原理是根據已有的模板,生成一個函數,但是由于bind()不知道生成的函數執行的時候,傳遞進來的參數是否還有效。所以它選擇參數值傳遞而不是引用傳遞。如果想引用傳遞,std::ref和std::cref就派上用場了。如下例子:
#include <functional>
#include <iostream>void f(int& n1, int& n2, const int& n3)
{std::cout << "In function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';++n1; // increments the copy of n1 stored in the function object++n2; // increments the main()'s n2// ++n3; // compile error
}int main()
{int n1 = 1, n2 = 2, n3 = 3;std::function<void()> bound_f = std::bind(f, n1, std::ref(n2), std::cref(n3));n1 = 10;n2 = 11;n3 = 12;std::cout << "Before function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';bound_f();std::cout << "After function: " << n1 << ' ' << n2 << ' ' << n3 << '\n';
}
輸出:
Before function: 10 11 12
In function: 1 11 12
After function: 10 12 12
上述代碼在執行std::bind后,在函數f()中n1的值仍然是1,n2和n3改成了修改的值。說明std::bind使用的是參數的拷貝而不是引用。具體為什么std::bind不使用引用,可能確實有一些需求,使得C++11的設計者認為默認應該采用拷貝,如果使用者有需求,加上std::ref即可。