std::function和std::bind綁定器

C++11中std::function and std::bind

1. 可調用對象

C++中可調用對象的概念,有以下幾種定義:
1) 是一個函數指針
2) 是一個具有operator()成員函數的類對象
3) 是一個可被轉換爲函數指針的類對象
4) 是一個類成員(函數)指針

void fun(void){
	//...
}
struct Foo{
	void operator()(void){
		//...
	}
};
struct Bar{
	using fr_t = void (*)(void);
	static void fun(){
		//...
	}
	operator fr_t(void){
		return fun;
	}
};
struct A{
	int _a;
	void mem_func(void){
		//... 
	}
};

int main(){
	using FUN = void (*func_ptr)(void) ;  // 1.函數指針
	FUN func_ptr = fun;
	func_ptr();
	
	Foo foo;
	foo();                             // 2.仿函數

	Bar bar;      
	bar();                             // 3.可被轉換爲函數指針的類對象  
	
	void(A::*mem_func_ptr)(void)       // 4.類成員函數指針
		= &A::mem_func;
	int A::*mem_obj_ptr                // 類成員指針
		= &A::_a;
	A aa;
	(aa.*mem_func_ptr)();
	aa,.*mem_ob_ptr = 123;
}

上述(func_ptr,foo,bar,mem_func_ptr,mem_obj_ptr)都被稱作可調用對象。上述的可調用對象並沒有函數類型或者函數引用類型(只有函數指針),這是因爲函數類型不能直接用來做定義對象,而函數引用更像const函數指針。

2. 可調用對象包裝器std::function

std::function是可調用對象的包裝器,它是一個類模板,可以容納除了類成員(函數)指針之外的所有可調用對象。通過指定它的模板參數,它可以用統一的方式處理函數,函數對象,函數指針,並允許保存和延遲執它們。

2.1 std::function的基本用法

#include <iostream>
#include <functional>
void func(){
    std::cout <<__FUNCTION__<< std::endl;
}
class Foo{
    public:
    static int foo_func(int a){
        std::cout << __FUNCTION__ << "(" << a << ") ->:\n";
        return a;
    }
};
class Bar{
    public:
    int operator()(int a){
        std::cout << __FUNCTION__ <<"("<< a <<") ->\n";
        return a;
    }
};

int main(){
    std::function<void(void)> fr1 = func; //綁定普通函數
    fr1();   

    std::function<int(int)> fr2 = Foo::foo_func; //綁定類靜態成員函數
    fr2(123);

    Bar bar;
    std::function<int(int)> fr3 = bar;  //綁定一個仿函數
    fr3(321);
}

從上述看到std::function的使用方法,當我們給std::function填入一個合適的函數簽名(即一個函數類型,只需要返回值和參數表)之後,它就可以變成了一個容納所有這類函數方式的”函數包裝器“。

2.2 std::function作爲回調

class A{
    std::function<void()> callback;
    public:
    A(const std::function<void()>& f):callback(f){}
    void notify(){
        callback();//回調到上層
    }
};
class Foo{
    public:
    void operator()(void){
        std::cout <<__FUNCTION__<<"is executable"<< std::endl;
    }
};

int main(){
    Foo foo;
    A aa(foo);
    aa.notify();
}

從上面的例子中可以看到,std::function可以取代函數指針的作用。因爲它可以保存函數延遲執行,所以適合做回調函數。

typedef void( *Func)(void);
using Func = void(*)(void);

使用函數指針類型作爲成員也可以完成,但是回調函數只能是普通函數而不能是仿函數等(需要再次轉型)。

2.3 std::function作爲函數入參

void call_when_even(int x,const std::function<void(int)>& f){
    if(!(x & 1)){
        f(x);
    }
    else{
        std::cout <<"並未觸發回調:"<<x<< std::endl;
    }
}

void output(int x){
    std::cout <<__FUNCTION__<<" parament value : "<<x<< std::endl;
}
int main(){
    int num = 10;
    while(num--)
        call_when_even(num,output);
}

從上述例子可以看出,std::function比函數指針更加靈活。

3. std::bind綁定器

std::bind用來可將於可調用對象與其參數一起綁定,綁定後的結果使用std::function進行保存,並延遲到任何我們需要的時候。
通俗講,它有兩大作用:
1)將可調用對象與其參數一起綁定爲一個仿函數。
2)將多元(參數n,n>1)可調用對象轉化成一元或者(n-1)元可調用對象,即只綁定部分參數。

3.1 std::bind基本用法

#include <iostream>
#include <functional>
void output(int x,int y){
    std::cout <<__FUNCTION__<<x<<","<<y<< std::endl;
}
int main(){
    std::bind(output,1,2)(); //輸出:1,2
    std::bind(output,std::placeholders::_1,2)(11); //輸出:11,2
    std::bind(output,2,std::placeholders::_1)(111);//輸出:2,111
    //std::bind(output,std::placeholders::_1,std::placeholders::_2)(1); //error  
    std::bind(output,std::placeholders::_1,std::placeholders::_2)(100,1000);
}

從上述可得對std::bind的返回結果直接調用。可以看到,std::bind可以綁定所有參數,也可以僅僅綁定部分參數。

3.2 std::function和std::bind配合使用

class A{
    public:
    int i_ = 100;
    void output(int x,int y){
        std::cout <<__FUNCTION__<<":"<<x<<","<<y<< std::endl;
    }
};
int main(){
    A a;
    std::cout <<a.i_<< std::endl;
    std::function<void(int,int)> fr = std::bind(&A::output,&a,std::placeholders::_1,std::placeholders::_2);
    fr(1,2);
    std::function<int&(void)> fr_i = std::bind(&A::i_,&a);
    fr_i() = 123;
    std::cout <<a.i_<< std::endl;
}

通過std::function和std::bind的配合,對所有可調用對象均有了統一調用方法。

3.3 std::bind1st,std::bind2nd,std::bind

//查找元素大於10的元素個數
int count = std::count_if(cool.begin(),cool.end(),std::bind1st(std::less<int>(),10);
//等與上式子
int count = std::count_if(cool.begin(),cool.end(),std::bind2nd(std::greater<int>(),10));

//查找元素小於10的元素個數
int count = std::count_if(cool.begin(),cool.end(),std::bind1st(std::greater<int>(),10));
//等於上式子
int count = std::count_if(cool.begin(),cool.end(),std::bind2nd(std::less<int>(),10));

//查找元素大於10的元素個數
using std::placeholders::_1;
int count = std::count_if(cool.begin(),cool.end(),std::bind(std::less<int>(),10,_1));
//查找元素小於10的元素個數
int count = std::count_if(cool.begin(),cool.end(),std::bind(std::less<int>,_1,10));

std::placeholders 佔位符合理使用,就不要糾結使用bind1st還是bind2nd。

3.4 使用std::bind組合函數

//判斷大於5小於10的元素(左開右閉)
auto f = std::bind(std::logical_and<bool>(),
	std::bind(std::greater<int>(),std::placeholders::_1,5),
	std::bind(std::less_equal<int>(),std::placeholders::_1,10));
int count = std::count_if(cool.begin(),cool.end(),f);

3.5 使用std::bind和std::function進行黑盒測試

typedef std::function<bool(const char*,int)> REC;    //回調函數類型
Type func(int ,int,REC);

在某個類中存在函數func,需要參數int,int,REC。於是在調用時可能如下:

bool awk(cosnt char *str,int b);
func(10,10,awk);

但實際這樣的函數在進行測試不能解決問題(需要重載或者修改原函數接口,而這麼做不符合軟件的開閉原則),因而需要使用lambda或者std::bind或者函數對象。

//std::bind
bool sawk(std::string &s,const char* str,int b);
std::string s;
auto t1 = std::bind(sawk,std::ref(s),std::placeholders::_1,std::placeholders::_2);
func(2,1,t1);

// lambda
std::string s;
auto t2 = [&s](const char*,int b)->bool {return true;};
func(2,1,t2);

//function object
class TS{
public: 
    bool operator()(const char *str,int a);
private:
    std::string d;
};
TS t3;
func(2,1,std::ref(t3));

通過傳入std::bind綁定的函數,lambda,以及仿函數得到的函數符合傳入REC的類型,但是可以額外附帶的參數可以完成測試功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章