目录
1.一个引例
2.function
什么是function?
function模板原型
function的使用
使用示例代码
使用function解决引例中的问题
3.bind
什么是bind?
如何理解bind?
bind的使用
4.function和bind总结
1.一个引例
看下面这一段代码并猜测一个结果:
#include <iostream>
using namespace std;// 函数模板
template<class F, class T>
T CallBackFunc(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}// 普通函数
int f(int t)
{return t += 10;
}// 仿函数
struct Functor
{int operator()(int t){return t += 20;}
};int main()
{// 传入函数名cout << CallBackFunc(f, 10) << endl;// 传入函数对象cout << CallBackFunc(Functor(), 10) << endl;// 传入lamber表达式cout << CallBackFunc([](int x) { return x += 30; }, 10) << endl;return 0;
}
结果如图所示:
在这段代码中,使用同一个函数模板的时候,传入了三个不同类型的可调用对象作为参数,从count 的值我们可以看出,CallBackFunc这个函数模板实例化生成了三份不同的代码,但是这三个可调用对象的参数、返回值、甚至功能都是一样的,所以完全没有必要生成三份代码;也就是说,如此丰富的可调用对象类型,可能会导致模板的效率低下。有没有什么办法可以让CallBackFunc这个函数模板只实例化生成一份代码呢?于是在C++11中引入了function这个类模板用来解决该问题。
2.function
什么是function?
function也叫包装器,是C++11标准库中的一个类模板,该模板可以用来对可调用对象进行包装。提供统一的类型和访问方式。
function模板原型
template <class Ret, class... Args>
class function<Ret(Args...)>;
其中Ret是可调用对象的返回值类型,Args是可调用对象的参数列表。
function的使用
1.使用function的时候需要包含<functional>这个头文件。
2.function的使用格式如下:
function<Ret,(Args)> 变量名 = 可调用对象
其中:Ret是可调用对象的返回值类型,Args是可调用对象参数列表中的参数类型;可调用对象可以是函数指针,函数对象,lambda表达式,当然,也可以是类域中的成员函数。
需要注意的是:
1.如果包装的是类域中的静态成员函数,可以省略取地址。
2.如果包装的是类域中的非静态成员函数,不可以省略取地址。而且还需要注意非静态成员函数中的第一个参数为指向当前对象的this指针,使用包装器的时候,需要显示的给出。
使用示例代码
int func(int a, int b)
{return a + b;
}struct Functor
{int operator()(int a, int b){return a+b;}
};class Add
{
public:static int add_int(int a, int b){return a + b;}double add_double(double a, double b){return a + b;}
};int main()
{// 使用包装器包装普通函数function<int(int, int)> fc1 = func;cout << fc1(1, 1) << endl;// 使用包装器包装 类的静态成员函数function<int(int, int)> fc2 = Add::add_int; //&可以省略cout << fc2(1, 1) << endl;// 使用包装器包装 类的非静态成员函数Add a;function<double(Add*, double, double)> fc3 = &Add::add_double;//&不可省略cout << fc3(&a, 1, 1) << endl;// 使用包装器包装lambda表达式function<int(int, int)> fc5 = [](int a, int b) {return a + b; };cout << fc5(1, 1) << endl;// 使用包装器包装函数对象function<int(int, int)> fc6 = Functor();cout << fc6(1,1) << endl;return 0;
}
使用function解决引例中的问题
还是上面的例子,但是我们使用function将这三个可调用对象进行了包装,包装成了统一的类型function<int(int)> ;大家可以猜一下结果是什么?
#include <iostream>
#include <functional>
using namespace std;// 函数模板
template<class F, class T>
T CallBackFunc(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}int func(int t)
{return t += 10;
}struct Functor
{int operator()(int t){return t += 20;}
};int main()
{// 包装函数指针function<int(int)> f1 = func;// 包装函数对象function<int(int)> f2 = Functor();// 包装lambda表达式function<int(int)> f3 = [](int a = 10) {return a + 30; };CallBackFunc(f1, 10);CallBackFunc(f2, 10);CallBackFunc(f3, 10);return 0;
}
代码结果:
能够观察到,count的值依次递增,并且count的地址也是一样的,说明CallBackFunc这个函数模板只实例化生成了一份代码。
可以得出结论,function可以对可调用对象进行再封装,统一可调用对象的类型,解决模板参数为可调用对象类型,类型不一致导致的模板实例化多份导致模版效率低下的问题。
3.bind
什么是bind?
概念:bind是 C++11 标准库中的一个函数模板,它定义在 <functional> 头文件中。bind的主要作用是接收一个可调用对象,将可调用对象的参数进行绑定或重新排列后,返回一个新的可调用对象,这个新的可调用对象在调用时会将绑定的参数传递给原始的可调用对象。
我们可以这样理解bind:将bind看做是一个通用的函数适配器,它接收一个可调用对象,生成一个新的可调用对象 来适应原对象的参数列表。
如何理解bind?
概念简直晦涩难懂,要想理解bind,我们可以先使用一下bind。
bind的使用格式:auto newCallable = bind(callable,arg_list);
其中,callable 是一个可调用对象,表明我们要绑定哪个函数,arg_list是callable 的参数列表,对应给定的callable的参数,newCallable 是bind之后,生成的新的可调用对象;当我们调用newCallable这个新的可调用对象时,newCallable会调用callable,并将我们调用newCallable 时所给定的参数传给callable中对应的参数。
一个简单的使用示例:
在这个示例中,我们有一个Log函数,我们想要用它来打印一句提示信息 “日志编号:数字”,如果直接使用Log函数的话,我们需要传入两个参数,但是在该需求中,第一个参数总是固定不变的,所以我们可以使用bind。
通过使用bind绑定Log函数时,首先给定第一个参数Log,表明我们要绑定Log函数,第二个参数设置为 “日志编号:” ,表示将Log函数的第一个参数固定为 “日志编号:” ,但是我们并不想固定Log函数的第二个参数,可以用一个 std::placeholders::_1来表示还需要一个参数,相当于占位符,这个参数在调用bind返回的对象 f 时,显示的给出,这样一来,就方便了我们对函数的使用了。
你可能会说,直接在Log函数中给缺省值也能实现相同的功能。没错,通过bind固定参数,其实就可以看做给函数设置缺省值,但是在一些回调的场景中,我们并没有完整的callable的参数,这个时候就可以调用 通过bind来绑定参数后生成的新的可调用对象,从而减小传参成本。更神奇的是,调用这个新的可调用对象,其实调用的还是旧的可调用对象。缺省参数无法解决这种问题。
注意一下:bind的第一个参数表明的是被调用函数的地址。
void Log(const std::string& str,int num)
{cout << str << num << endl;
}int main()
{// 不使用bind调用Log函数Log("日志编号: ",0);// 使用bind调用Log函数auto f = bind(Log,"日志编号:",std::placeholders::_1);f(1);return 0;
}
说明:你可能会疑问这个 std::placeholders::_1是什么?
这其实就是用来对我们不想绑定的参数进行占位的,placeholders中还有_1, _2, _3, _4, _5, _6……等等,但是使用的时候,必须根据未绑定的参数个数给出,不能随意乱给定。在上面这个例子中,我们只有一个参数没有绑定,所以我们只能给_1,而不能给_2,_3等等。数值_n中的n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。
但是_1,_2在bind中的顺序并不完全固定,我们可以把_2写在_1的前面,利用这个特性就实现了参数顺序的调换。
下面代码输出为:1
bind的使用
bind用于绑定可调用对象,C++中的可调用对象有函数指针,函数对象,lambda表达式,还有类中的静态成员变量 和 类中的非静态成员变量。我们依次学习。
一:使用bind绑定普通函数
int func(int a, int b)
{return a + b;
}int main()
{/*使用bind绑定普通函数*/auto fc1 = bind(func,placeholders::_1,1);cout << fc1(1) << endl;return 0;
}
二:使用bind绑定 类的非静态成员函数
class Add
{
public:static int add_int(int a, int b){return a + b;}double add_double(double a, double b){return a + b;}
};int main()
{/*使用bind绑定 类的非静态成员函数*//*注意:非静态成员函数需要使用 对象 or 对象的指针 or 对象的应用进行调用*/// 方法一:this指针的位置传递匿名对象auto fc2 = bind(&Add::add_double,Add(),std::placeholders::_1, 2); //&不可以省略cout << fc2(1) << endl;// 方法二:this指针的位置传递对象Add aa;auto fc3 = bind(&Add::add_double, aa, std::placeholders::_1, 2); //&不可以省略cout << fc3(1) << endl;// 方法三:this指针的位置传递对象的地址auto fc4 = bind(&Add::add_double, &aa, std::placeholders::_1, 2); //&不可以省略cout << fc4(1) << endl;return 0;
}
三:使用bind绑定 类的静态成员函数
class Add
{
public:static int add_int(int a, int b){return a + b;}double add_double(double a, double b){return a + b;}
};int main()
{/*使用bind绑定 类的静态成员函数*//*静态成员函数的调用不依赖类的实例,因此,他们在绑定时,不需要 对象的引用 or 指针*///方法一:不省略bind第一个参数中的&auto fc5 = bind(&Add::add_int, std::placeholders::_1, 3); cout << fc5(1) << endl;//方法二:省略bind第一个参数中的&auto fc6 = bind(Add::add_int, std::placeholders::_1, 3);cout << fc6(1) << endl;return 0;
}
四:使用bind绑定lambda表达式
int main()
{/*使用bind绑定lambda表达式*/auto fc7 = bind([](int a, int b) {return a + b; } ,std::placeholders::_1,4);cout << fc7(1) << endl;return 0;
}
五:使用bind绑定函数对象
struct Functor
{int operator()(int a, int b){return a+b;}
};int main()
{/*使用bind绑定函数对象*/auto fc8 = bind(Functor(),std::placeholders::_1,5);cout << fc8(1) << endl;return 0;
}
4.function和bind总结
function是对可调用对象的再封装。由于历史的原因,C++中具有丰富的可调用对象,如函数指针,函数对象,lambda表达式……如此众多的可调用对象在使用模板的时候就会造成模板的效率低下,于是C++11提供function对可调用兑现进行再封装,达到统一可调用对象类型的目的。
bind是用来调整可调用对象的参数的一个函数模板。既可以调整可调用对象参数的个数(在一些回调场景中频繁使用),也可以调整可调用对象参数的顺序(这玩意意义不大)。