std::function与std::bind的使用总结
文章目录
- std::function vs 函数指针
- std::function与std::bind联合使用
- 扩展一下:
C++中函数指针的用途非常广泛,例如 回调函数,接口类的设计等,但函数指针始终不太灵活,它只能指向全局或静态函数,对于类成员函数、lambda表达式或其他可调用对象就无能为力了,因此,C++11推出了std::function与std::bind
类模版std::function是一种通用、多态的函数封装。std::function的实例可以对任何可以调用的目标实体进行存储、复制、和调用操作,这些目标实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。
函数指针这类可调用实体,是类型不安全的,而std::function对象是对C++中现有的可调用实体的一种类型安全的包裹
std::function对象最大的用处就是在实现函数回调
可调用实体转换为std::function对象需要遵守以下两条原则:
- 转换后的std::function对象的参数能转换为可调用实体的参数;
- 可调用实体的返回值能转换为std::function对象的返回值。
std::function vs 函数指针
C++函数指针相信大家用的很多了,用法最广泛的应该就是先定义函数指针的类型,然后在声明一个函数指针的变量作为另一个函数的入参,以此作为回调函数,如下列代码所示:
typedef void (*PrintFinCallback)();
void print(const char *text, PrintFinCallback callback) {
printf("%s\n", text);
callback();
}
void printFinCallback() {
cout << "hhh" << endl;
}
print("test", printFinCallback);
在C++11之后,我们多了一种选择,std::function,使用它时需要引入头文件functional。std::function可以说是函数指针的超集,它除了可以指向全局和静态函数,还可以指向仿函数,lambda表达式,类成员函数,甚至函数签名(C++中的函数签名(function signature):包含了一个函数的信息,包括函数名、参数类型、参数个数、顺序以及它所在的类和命名空间,普通函数签名并不包含函数返回值部分)不一致的函数
,可以说几乎所有可以调用的对象都可以当做std::function,当然对于后两个需要使用std::bind进行配合,而至于指向其他类型可以参考以下代码:
#include <functional>
#include <iostream>
using namespace std;
//重命名数据类型std::function<void ()>为PrintFinFunction,
//可以用using替代typedef,例如using PrintFinFunction std::function<void ()>
typedef std::function<void ()> PrintFinFunction;
//回调函数的定义
void print(const char *text, PrintFinFunction callback) {
printf("%s\n", text);
if (callback)
callback();
}
/***
回调函数的实例化
**/
// 普通函数
void printFinCallback() {
cout << "Normal callback" << endl;
}
// 类静态函数
class Test {
public:
static void printFinCallback() {
cout << "Static callback" << endl;
}
};
// 仿函数,重载()运算符
struct Functor {
void operator() () {
cout << "Functor callback" << endl;
}
};
// Lambda表达式也可以在外部定义,不在main里面定义
auto lambda = []()->void{ cout << "Lambda callback" << endl;return 0; };
int main(int argc,char *argv[]){
print("test 1", printFinCallback);
print("test 2", Test::printFinCallback);//类的静态函数需要写作用域,静态函数不属于任何对象只属于该类
print("test 3", Functor());//仿函数的调用其实会先创建一个临时的类对象,类对象调用构造函数将this指向该对象
// Lambda表达式
print("test 4", [] () {
cout << "Lambda callback" << endl;
return 0;
});
}
std::function填补了函数指针的灵活性,但会对调用性能有一定损耗,经测试发现,在调用次数达10亿次时,函数指针比直接调用要慢2秒左右,而std::function要比函数指针慢2秒左右,这么少的损耗如果是对于调用次数并不高的函数,替换成std::function绝对是划得来的。
std::function与std::bind联合使用
std::bind的作用:对可调用实体(函数指针,仿函数,lambda表达式)的一种封装,这种封装能起到预绑定参数的作用,预绑定的参数是以值传递的形式,不预绑定的参数要用std::placeholders(占位符)的形式占位,从_1开始,依次递增,是以引用传递的形式
std::bind的返回值是可调用实体,可以直接赋给std::function
刚才也说道,std::function可以指向类成员函数和函数签名不一样的函数,其实,这两种函数都是一样的,因为类成员函数都有一个默认的参数,this,作为第一个参数,这就导致了类成员函数不能直接赋值给std::function
,这时候我们就需要std::bind了,简言之,std::bind的作用就是转换函数签名,将缺少的参数补上,将多了的参数去掉,甚至还可以交换原来函数参数的位置,具体用法如下列代码所示:
typedef std::function<void (int)> PrintFinFunction;
void print(const char *text, PrintFinFunction callback) {
printf("%s\n", text);
if (callback)
callback(0);
}
// 类成员函数
class Test {
public:
void printFinCallbackInter(int res) {
cout << "Class Inter callback" << endl;
}
};
// 函数签名不一样的函数
void printFinCallback2(int res1, int res2) {
cout << "Different callback " << res1 << " " << res2 << endl;
}
Test testObj;
//std::function里定义的函数,只有一个参数,这里需要补充this参数,
//然后复用类成员函数里的第一个参数
auto callback5 = std::bind(&Test::printFinCallbackInter, testObj, std::placeholders::_1);
print("test 5", callback5);
//这里需要补充this 参数,复用第一个参数,然后补充第二个参数
auto callback6 = std::bind(&Test::printFinCallback2, testObj,std::placeholders::_1, 100);
print("test 6", callback6);
// 所以从本质上看 std::bind转化了函数签名,把类成员函数转化为普通成员函数
// &Test::printFinCallbackInter 需要两个参数,经过bind后变成需要一个参数
// &Test::printFinCallback2 需要三个参数,经过bind后变成需要一个参数。
从上面的代码中可以看到,std::bind的用法就是第一个参数是要被指向的函数的地址,为了区分,这里std::bind语句的左值函数为原函数,右值函数为新函数,那么std::bind方法从第二个参数起,都是新函数所需要的参数,缺一不可【第二个参数是对象,可以用this代替,因为this指向的就是由类创建的对象】,而我们可以使用std::placeholders::_1或std::placeholders::_2等等作为占位符,来使用原函数的参数,_1就是原函数的第一个参数,如此类推。
一旦bind补充了缺失的参数,那么以后每次调用这个function时,那些原本缺失的参数都是一样的,举个栗子,上面代码中callback6,我们每次调用它的时候,第二个参数都只会是100
扩展一下:
我们知道直接绑定这个类成员函数,就会报错,如果不用bind绑定,给它补充this参数,但是绑定仿函数却可以,这里一定有人会有疑问,本身仿函数,也是有this指针的,为啥成员函数不行,仿函数就可以呢?
这里我们把第一例子,再添加一个构造函数就清晰了
#include<iostream>
using namespace std;
typedef std::function<void ()> PrintFinFunction;
void print(const char *text, PrintFinFunction callback) {
std::cout<<test<<std::endl;
if (callback)
callback();
}
// 仿函数,重载()运算符
struct Functor {
Functor(){
std::cout<<"Functor construct enter" << std::endl;
}
void operator() () {
cout << "Functor callback" << endl;
}
};
int main(int argc,char *argv[]){
print("test 3", Functor());
return 0;
}
这个测试后发现,调用仿函数的时候,会生成一个临时的对象,然后调用了构造函数,然后这个时候这个对象的this指针就传给了这个仿函数,也就是它已经补充了this指针,所以不会报错。
2.那么成员函数不行,是因为成员函数的第一个参数是this指针,而不能向仿函数那样直接调用方法那样,进行隐式的传递this,但是如果我们的回调函数的第一个参数是 类对象的指针,那我们也不需要这个bind了。看下面的例子
#include<iostream>
#include<functional>
using namespace std;
class Test;
typedef std::function<void (Test*p)> PrintFinFunction;//第一个参数是类对象指针,则不需要使用bind
void print(Test*t, PrintFinFunction callback) {
if (callback)
callback(t);
}
class Test {
public:
static void printFinCallback() {
cout << "Static callback" << endl;
}
void testadd(){
cout << "class Test callback " <<this->num<< endl;
}
private:
int num=6;
};
int main(){
Test temp;
print(&temp,&Test::testadd);
return 0;
}
类成员函数,类成员函数本身也是有一个地址,然后它是针对所有的类对象的,每一个类对象都可以调用这样的方法,类成员函数是唯一的,不论谁调用,其实你要给我第一个参数就可以了,我操作的是this指针,不论谁来,我操作的成员变量都是this的,至于你说的这个this是谁我是不关心的。
参考博文:
C++11新特性之std::function与std::bind
添加链接描述std::function与std::bind的使用总结