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的类型,但是可以额外附带的参数可以完成测试功能。