function包装器和bind包装器
- 包装器
- function包装器
- 为什么需要function
- function包装器
- function包装器的应用场景
- 逆波兰表达式求值
- bind包装器
- bind包装器的应用场景
包装器
包装器是用于给其他编程接口提供更一致或更合适的接口
由于函数调用可以使用函数名、函数指针、函数对象和lambda表达式,可调用类型太丰富导致模板的效率极低。包装器用于解决效率低的问题
function包装器
function是一种函数包装器,也叫做适配器。它可以对可调用对象进行包装,C++中的function本质就是一个类模板
为什么需要function
我们看如下例子:
template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}double f(double i)
{return i / 2;
}//仿函数
struct Functor
{double operator()(double d){return d / 3;}
};void test1()
{// 函数名cout << useF(f, 11.11) << endl;// 仿函数cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
}
useF中的f(x)
,f可能是什么呢?函数名?函数指针?仿函数对象?lambda表达式对象?这些都是可调用的类型对象,这就会导致模板的效率低下。怎么个低下法呢?我们执行上述代码,得到结果:
可以看到,上述代码产生了三个不同的静态变量count
,说明useF函数模板被实例化出了三份。之所以被实例化出三份,是因为f
、Functor()
、[](double d)->double { return d / 4; }
的类型并不相同。
那么为了提高效率,能不能让useF函数模板只实例化一份呢?换句话说,能不能让这三个类型都统一成一个类型?–function包装器
function包装器
std::function
本质是个类模板,在头文件<functional>
中
template <class Ret, class... Args>
class function<Ret(Args...)>; //Args...是参数列表
- Ret: 被调用函数的返回类型
- Args…:被调用函数的形参
根据上面的例子,被调用的函数是double f(double i)
,因此Ret和Args都是double
所以void test1()
中对于useF的调用可以写成这样:
void test1()
{// 函数名function<double(double)> f1 = f;// 仿函数对象function<double(double)> f2 = Functor();// lamber表达式对象function<double(double)> f3 = [](double d)->double { return d /4; };cout << useF(f1, 11.11) << endl;cout << useF(f2, 11.11) << endl;cout << useF(f3, 11.11) << endl;
}
运行结果如下:
可以看到只生成了一个静态变量count,说明只实例化出了一份函数。这样就提高了效率
function包装器的应用场景
- 场景一:上面所讲的提高模板的使用效率
- 场景二:将可调用对象的类型统一
针对场景二,进行详细讲解:
为什么要将这些可调用对象的类型进行统一呢?因为统一后,可以将不同的可调用对象都存储在同一个容器中,方便管理
以前想把那些可调用对象存在同一个vector中是几乎不可能的,现在借助包装器,将可调用对象类型统一后,就可以存储了
比如,延续上面的例子:
// 函数名function<double(double)> f1 = f;// 仿函数对象function<double(double)> f2 = Functor();// lamber表达式对象function<double(double)> f3 = [](double d)->double { return d / 4; };//写法一:vector<function<double(double)>> v = { f1,f2,f3 };//写法二:直接写:vector<function<double(double)>> v = { f,Functor(),[](double d)->double { return d / 4; } };
下面给出一个实例:
逆波兰表达式求值
150. 逆波兰表达式求值 - 力扣(LeetCode)
- 以前的做法:
int evalRPN(vector<string>& tokens) {stack<int> st;//将tokens中的数字依次入栈,若遇到操作符则出栈计算for(auto& str:tokens){if(str=="+" || str=="-" || str=="*" || str=="/"){int right=st.top();st.pop();int left=st.top();st.pop();switch(str[0]){case '+':st.push(left+right);break;case '-':st.push(left-right);break;case '*': st.push(left*right);break;case '/':st.push(left/right);break;}}else{st.push(stoi(str));//将字符串变成整型存入栈中}}return st.top();}
- 现在利用function包装器实现:
这里是一个命令对应一个动作,比如遇到”+“,就实现两个数相加。
(凡是类似这种场景,都可以利用包装器。就比如说linux的命令,就可以这样实现)
int evalRPN(vector<string>& tokens) {map<string,function<int(int,int)>> m ={{"+",[](int x,int y){return x+y;}},{"-",[](int x,int y){return x-y;}},{"*",[](int x,int y){return x*y;}},{"/",[](int x,int y){return x/y;}}};stack<int> st;//将tokens中的数字依次入栈,若遇到操作符则出栈计算for(auto& str:tokens){if(m.count(str)){int right=st.top();st.pop();int left=st.top();st.pop();st.push(m[str](left,right));}else{st.push(stoi(str));//将字符串变成整型存入栈中}}return st.top();}
上述代码还有一个优势,就是如果再增加运算符操作的话,只需在map这增加代码即可,其他地方不需要改动
这里为了简便就用了lambda表达式。当然也可以写函数名、仿函数对象,如下:
class Solution
{
public:static int add(int x, int y){return x+y;}struct sub{int operator()(int x,int y){return x-y;}};int evalRPN(vector<string>& tokens) {map<string,function<int(int,int)>> m ={{"+",add},{"-",sub()},{"*",[](int x,int y){return x*y;}},{"/",[](int x,int y){return x/y;}}};//……}
};
int add(int x, int y)
前面要加一个static是因为:这个函数是在类中,类成员函数一般都是由对象去调用的,这里只想要用它的地址 加上静态关系后,就不需要依靠对象调用了,光写函数名就能代表函数的地址了
bind包装器
bind也是一种函数包装器,也叫做适配器。它可以接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。 C++中的bind本质是一个函数模板,也在头文件<functional>
中
bind包装器的应用场景
- 调整函数的参数顺序
我们看如下代码:
int Sub(int x, int y)
{return x - y;
}int main()
{function<int(int, int)> rSub1 = bind(Sub, placeholders::_1, placeholders::_2);cout << rSub1(10, 5) << endl;function<int(int, int)> rSub2 = bind(Sub, placeholders::_2, placeholders::_1);cout << rSub2(10, 5) << endl;return 0;
}
这里的
placeholders
是个命名空间,_1
和_2
都是命名空间中的标识符
运行结果:
我们看改变的地方只有一处:
这就是利用bind实现的参数顺序的调换
我们理一下调用逻辑:
-
对于rSub1:
-
对于rSub2:
总结:
-
bind过后的函数传参时:第一个实参只会传给标识符
_1
,第二个实参只会传给标识符_2
,以此类推这里传几个实参,bind语句中就有几个
_
,且是对应死的。第一个实参就对应于_1
所以说,在上面写个
_3
是会报错的 -
bind传给原函数时:不再看什么标识符,按照位置关系进行传参
- 调整函数的传参个数(也就是将原本函数中的某一个参数绑定死)
看如下代码:
double Plus(int x, int y, double rate)
{return (x + y) * rate;
}int main()
{function<double(int, int)> plus1 = bind(Plus, placeholders::_1, placeholders::_2, 4.1);function<double(int, int)> plus2 = bind(Plus, placeholders::_1, placeholders::_2, 4.3);function<double(int, int)> plus3 = bind(Plus, placeholders::_1, placeholders::_2, 4.5);cout << plus1(5, 3) << endl;cout << plus2(5, 3) << endl;cout << plus3(5, 3) << endl;return 0;
}
这里就是将Plus函数的第三个参数rate固定
尽管缺省参数也可以实现将参数固定,但缺省参数只能固定一种情况,bind可以固定任意情况
此外,还要注意在写类型时,被绑定的参数的类型就不需要写在function中了
假如我最初函数写成这样:
那么bind语句就这样写:
注意这里还是_1和_2。因为这个是和bind后的函数的第几个实参对应的。
- 绑定类的成员函数
看如下代码:
class SubType
{
public:int sub(int x, int y){return x - y;}static int ssub(int a, int b, int rate){return (a - b) * rate;}
};int main()
{//bind静态成员函数function<int(int, int)> rssub = bind(SubType::ssub, placeholders::_1, placeholders::_2,3);cout << rssub(5, 3) << endl;//bind成员函数function<int(int, int)> rsub = bind(&SubType::sub,SubType(), placeholders::_1, placeholders::_2);cout << rsub(5, 3) << endl;return 0;
}
注意点:
- 绑定类成员函数时,一定要声明这个函数在哪个类中,如上
SubType::
- bind成员函数时,需要在函数名前面加上&(静态成员函数可加可不加)
- bind成员函数时,需要增加一个参数:类对象或者类对象的指针
其实,绑定的底层也是仿函数