目录
引入
一、function包装器
1.1包装器使用
1.2包装器解决类型复杂
二、bind包装器
引入
在我们学过的回调中,函数指针,仿函数,lambda都可以完成,但他们都有一个缺点,就是类型的推导复杂性,从而会导致某一问题,例如:
#include <iostream>
using namespace std;
// 定义一个仿函数
struct DivideByTwo {
double operator()(double num) {
return num / 2.0;
}
};
// 定义一个函数
double divideByThree(double num) {
return num / 3.0;
}
template <class F,class T>
T useF(F f, T x)
{
return f(x);
}
int main() {
// 直接使用仿函数
cout << useF(DivideByTwo(), 10.0) << endl;
// 直接使用函数指针
cout << useF(divideByThree, 10.0) << endl;
//lambda表达式
cout << useF([](double d) {return d / 4; }, 10.0) << endl;
return 0;
}
输出结果:
在上述示例中,由于类型的复杂,useF函数模板需要实例化三份,从而使得代码变得相对冗余,面对这种问题,function包装器可以带来一定的优化。那么接下来讲讲包装器的用法,然后来解决这个问题。
一、function包装器
1.1包装器使用
function包装器也叫适配器,是一个类模板。因为是一个适配器,所以可以适配出函数指针,仿函数,lambda的用法。
std::function包装器在头文件functional>
类模板原型如下:
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明: Ret: 被调用函数的返回类型
Args…:被调用函数的形参
#include <iostream>
using namespace std;
#include <functional>
int f(int a, int b)
{
return a + b;
}
struct Functor
{
public:
int operator() (int a, int b)
{
return a + b;
}
};
class Plus
{
public:
static int plusi(int a, int b)
{
return a + b;
}
double plusd(double a, double b)
{
return a + b;
}
};
int main()
{
// 函数名(函数指针),
function<int(int, int)> func1 = f;//将函数指针传给包装器,就可以适配出具有相应函数指针的功能
cout << func1(1, 2) << endl;//调用的就是函数f,被调用函数的返回值类型是int,形参是int,int
// 函数对象
function<int(int, int)> func2 = Functor(); //将仿函数对象传给包装器,就可以适配出仿函数
cout << func2(1, 2) << endl;//调用的就是仿函数operator(),被调用仿函数的返回值类型是int,形参是int,int
// lambda表达式
function<int(int,int)> func3 = []( int a, int b){return a + b; };//将lambda传给包装器,就可以适配出lambda
cout << func3(1, 2) << endl;//调用的就是lambda,而其底层是仿函数,即调用对应的operator(),被调用仿函数的返回值类型是int,形参是int,int
// 类的成员函数,注意:成员函数取地址必须加&,由于是在类外,所以还得加上类域
function<int(int, int)> func4 = &Plus::plusi;//将静态成员函数指针传给包装器,就可以适配出具有相应成员函数指针的功能
cout << func4(1, 2) << endl;//调用的就是plusi函数
function<double(Plus*, double, double)> func5 = &Plus::plusd;//由于是非静态成员函数,其多了一个this指针,所以得多带上一个参数
//cout << func5(&Plus(), 1.1, 2.2) << endl;//由于plus()是一个匿名对象,属于右值,右值不能够取地址,所以不能这样写
Plus ps;//得额外定义一个对象
cout << func5(&ps, 1.1, 2.2) << endl;
//还可以这种写法,相当于用对象去调用了这个成员函数,可以认为是编译器的特殊处理
function<double(Plus, double, double)> func6 = &Plus::plusd;
cout << func6(Plus(), 1.1, 2.2) << endl;
return 0;
}
输出结果:
1.2包装器解决类型复杂
回到最开始引入的问题, 使用包装器就可以解决,如下:
int main() {
// 直接使用仿函数
function<double(double)> func1 = DivideByTwo();
cout << useF(func1, 10.0) << endl;
// 直接使用函数指针
function<double(double)> func2 = divideByThree;
cout << useF(func2, 10.0) << endl;
//lambda表达式
function<double(double)> func3 = [](double d) {return d / 4; };
cout << useF(func3, 10.0) << endl;
return 0;
}
输出结果:
function<double(double)>
作为参数传递给useF
函数模板。那么,模板会根据这个特定的参数类型(即接受一个double
类型参数并返回double
类型值的可调用对象)进行一次实例化。无论以这种类型的function
调用useF
函数多少次,只要参数类型不变,都只会进行这一次实例化。相比较传入不同类型实例化有效地减少了实例化的次数和复杂性
二、bind包装器
也是定义在functional文件中,是一个函数模板,
原型如下:
template class Fn, class... Args>
bind (Fn&& fn, Args&&... args);
调用bind的一般形式:auto newCallable = bind(callable,arg_list);
也是一个函数包装器(适配器),callabl代表可调用函数,arg_list用来接收绑定多个参数,以逗号分割,从而生成一个新的参数列表来替换原函数的参数列表,并将该bind函数对象赋值给newCallable,当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。
arg_list中的参数可能包含形如_n的名字(属于placeholders类域),其中n是一个整数,这些参数是“占位符”,表示 newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示newCallable中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。概念还是抽象的,通过例子来演示:
#include <iostream>
using namespace std;
#include <functional>
int Plus(int a, int b)
{
return a + b;
}
class Sub
{
public:
int sub(int a, int b)
{
return a - b;
}
};
int main()
{
//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定,并用function包装器包装bind
function<int(int, int)> func1 = bind(Plus, placeholders::_2, placeholders::_1);//绑定参数,_2代表func1的第二个参数在第一个位置,_1代表func2的第一个参数在第二个位置
//相当于第一个参数替换成了2,第二个替换成了1,即1传给了b,2传给了a
cout << func1(1, 2) << endl;//调用Plus函数
//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
//func2的类型为 function<void(int, int, int)> 与func1类型一样
//表示绑定函数 plus 的第一,二为: 1, 2
auto func2 = bind(Plus, 1, 2);//绑定参数,这些参数替换原来的参数,即func2的参数替换成了1,2
cout << func2(2,3) << endl;//调用Plus函数
// 绑定成员函数
std::function<int(int, int)> func3 = std::bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
cout << func3(1, 2) << endl;
std::function<int(int, int)> func4 = std::bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);
cout << func4(1, 2) << endl;
return 0;
}
输出结果:
总结一句话,bind通过更改原函数参数列表来包装新函数。
end~