前言
關於std::bind()
對普通函數的包裝作用,在之前的總結文章《std::bind(一):包裝普通函數》已經舉例說明過了,後來發現丟下了普通函數嵌套包裝的情況,所以在這篇文章中繼續說明一下,然後重點總結std::bind()
函數對成員函數的包裝,在面向對象的大潮還未褪去的今天,還是成員函數見到的更多一些,所以講講對它的包裝。
普通函數嵌套包裝
實際上就是普通函數包裝的變形和組合,直接寫個例子吧,如果test1_1()
、test1_2()
、test1_3()
三個函數的輸出結果都答對了就說明已經掌握了。
void func(int n1, int n2, int n3)
{
cout << n1 << ' ' << n2 << ' ' << n3 << endl;
}
int calc_value(int c1)
{
return c1 * c1;
}
void calc_value2(int c1)
{
int result = c1 * c1;
}
void test1_1()
{
auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value, placeholders::_2));
f1(11, 2); // same as call func(11, 101, calc_value(2))
}
void test1_2()
{
int n = 2;
auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value, std::ref(n)));
n = 4;
f1(11, 2); // same as call func(11, 101, calc_value(44)) 多出的參數2無人使用
}
void test1_3()
{
auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value2, placeholders::_2));
//f1(11, 2); // 編譯出錯,無法將參數 3 從“void”轉換爲“int”
}
// 11 101 4
// 11 101 16
第一個test1_1
函數的邏輯應該很容易理解,就是把函數calc_value(2)的返回值作爲函數func
的第三個參數,而函數test1_2
中利用了std::ref()
傳遞引用的功能,將變量n
作爲引用變量進行傳遞,在包裝調用之前可以感知到參數n的變化。
其實難點在第三個函數test1_3
,可能大家知道這裏會報錯,因爲我們需要返回值但是卻包裝了一個沒有返回值的函數,但其實把第二行註釋掉之後,程序就可以成功編譯,也就是說包裝錯誤的函數如果不被調用,是不會報錯的,這一點和模板函類不使用就不會創建很相似,最終是相同的。
包裝類成員
在深入學習std::bind()
這個函數之前一直以爲它只能用來包裝函數,後來通過進一步瞭解發現它還能用來包裝成員變量,我們一起來看一下簡單的實現方法。
成員函數的包裝
這裏我們不考慮靜態成員函數,因爲靜態函數沒有this
指針,和普通的函數基本一樣,在用法上也沒有很大的差異,所以此處的包裝只考慮成員非靜態函數,可以嘗試分析以下幾個例子。
class CTest
{
public:
CTest() {}
~CTest() {}
public:
void func1(int n1, int n2)
{
cout << "func1 " << n1 << ' ' << n2 << endl;
}
int n_public;
private:
void func2(int n1, int n2)
{
cout << "func2 " << n1 << ' ' << n2 << endl;
}
int n_private;
};
void test2_1()
{
CTest testObj;
auto f2 = std::bind(&CTest::func1, testObj, 101, placeholders::_1);
f2(1); // same as call testObj.func1(101, 1)
}
void test2_2()
{
CTest testObj;
auto f2 = std::bind(&CTest::func1, &testObj, 101, placeholders::_1);
f2(2); // same as call testObj.func1(101, 2)
}
void test2_3()
{
CTest testObj;
CTest& obj = testObj;
auto f2 = std::bind(&CTest::func1, obj, 101, placeholders::_1);
f2(3); // same as call testObj.func1(101, 3)
}
void test2_4()
{
CTest testObj;
auto f2 = std::bind(&CTest::func1, placeholders::_1, placeholders::_2, 101);
f2(testObj, 4); // same as call testObj.func1(4, 101)
}
void test2_5()
{
CTest testObj;
// auto f2 = std::bind(&CTest::func2, &testObj, 101, placeholders::_1);
// 編譯錯誤,func2不可訪問
}
//func1 101 1
//func1 101 2
//func1 101 3
//func1 4 101
前三個函數tes2_1()
、tes2_2()
、tes2_3()
的作用基本一致,就是將一個類的非靜態成員函數和對象綁定,並且可以動態綁定一些參數,三種調用方式都可以,暫時沒有發現什麼問題,大家知道區別的可以指導我一下,我補充上來,需要注意的是函數std::bind()
參數個數需要在原函數參數個數的基礎上加兩個,第一個很明顯就是函數名,而第二個必須是調用這個函數的對象,至於傳遞的是指針還是引用都沒有什麼問題,這兩個參數過後纔是真正的原函數的參數。
函數test2_4()
相對於前三個來說更加靈活,將對象也最爲參數在調用時傳入,這就相當於把一個成員函數看成,一個普通函數然後在第一個參數前加this指針的形式,後面這種調用方式在查看C++調用堆棧時應該很容易看到,本質上是一樣,其實這裏還有一個對象傳遞的問題,我們在成員變量時再測試一下。
函數test2_5()
出現了編譯錯誤,原因是在使用函數std::bind()
的時候也要考慮到原函數的訪問權限,在測試函數中訪問對象的私有函數顯然是不可以的。
成員變量的包裝
void test3_1()
{
CTest testObj;
auto f3 = std::bind(&CTest::n_public, testObj);
f3(1) = 10;
cout << f3(1) << endl;
cout << testObj.n_public << endl;
}
void test3_2()
{
CTest testObj;
auto f4 = std::bind(&CTest::n_public, placeholders::_1);
f4(testObj) = 4;
cout << f4(testObj) << endl;
cout << testObj.n_public << endl;
}
void test3_3()
{
CTest testObj;
auto f3 = std::bind(&CTest::n_public, std::ref(testObj));
f3(1) = 11;
cout << f3(1) << endl;
cout << testObj.n_public << endl;
}
//10
//-858993460
//4
//4
//11
//11
這個成員變量的綁定測試結果,有沒有讓人意想不到呢?或者說這種f3(1) = 10;
寫法已經讓人很驚訝了,其實我在寫例子的時候就是簡單試試,沒想到這樣寫居然可以,看起來好像把一個值賦值給了一個函數一樣。
函數test3_1()
的第二個輸出可能有點想不到,但是看到結果是有些人可能就明白了,因爲在上一篇裏提到“std::bind()
函數中的參數在被複制或者移動時絕不會以引用的方式傳遞,除非你使用了std::ref()
或者std::cref()
包裝的參數”。
因爲沒有使用std::ref()
函數包裝,所以std::bind()
函數綁定的testObj
對象實際上是原對象的副本,那麼針對於副本的操作和修改自然就不會反應到原對象上,這也就是打印testObj.n_public
會輸出隨機值的原因。
函數test3_2()
在綁定時並沒有具體到特定的對象,而是使用了placeholders::_1
佔位符,這樣生成的函數,在調用的時候再傳入操作對象,那麼此時修改對象屬性就可以起作用了。
函數test3_3()
是針對於函數test3_1()
的,添加了std::cref()
包裝的原對象,可以通過綁定後的函數修改。
總結
std::bind()
函數可以嵌套綁定std::bind()
函數綁定成員函數時,函數名參數後面需要緊跟着類的對象作爲參數std::bind()
不僅可以綁定普通函數、成員函數、還可以綁定成員變量