1. 什么是包装器
C++中的包装器是一种设计模式,用于将一个复杂或底层的接口进行封装,以便提供一个更简洁、易用的接口。包装器可以包装任何类型的可调用实体,如函数,成员函数,函数指针,仿函数对象,lambda表达式等。
包装器在资源管理、接口封装、类型安全等方面有广泛应用。
在实际编程中,包装器可以用于回调机制、事件处理、异步编程等场景。它们提供了灵活的函数调用机制,使得代码更加模块化和可重用。
综上所述,C++包装器是提升代码可读性、可维护性和性能的有力工具。通过使用标准库(<functional>头文件)中提供的包装器类,开发者可以更方便地处理各种可调用对象。
2. function
在C++标准库中,std::function是一个模板类,它用作一个通用的函数包装器,能够存储、复制和调用任何可调用的目标。
template <class T>class function; template <class Ret, class... Args>class function<Ret(Args...)>;
2.1 包装非成员函数
在对可调用对象进行包装的时候,需要在类型参数列表中指定可调用对象的返回值和参数列表:
#include<functional>#include<iostream>using namespace std;int f(int a, int b){return a + b;}struct Functor{ public:int operator() (int a, int b){return a + b;}};int main(){// 包装各种可调用对象function<int(int, int)> f1 = f;function<int(int, int)> f2 = Functor();function<int(int, int)> f3 = [](int a, int b) {return a + b; };cout << f1(1, 1) << endl;cout << f2(1, 1) << endl;cout << f3(1, 1) << endl;return 0;}
可以认为是编译器对包装器做的特殊处理吧。
2.2 成员函数
与普通函数不同的是,不能直接使用成员函数的函数名来进行包装,必须对其进行取地址(&)。
注意:(1) 访问成员函数要指定类域;(2) 普通成员函数的参数列表中有隐含的this指针。
#include<functional>#include<iostream>using namespace std;class Plus{public:Plus(int n = 10): _n(n){}static int plusi(int a, int b){return a + b;} double plusd(double a, double b){return (a + b) * _n;}private:int _n;};int main(){// 包装静态成员函数// 成员函数要指定类域并且前面加&才能获取地址function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装普通成员函数// 普通成员函数还有⼀个隐含的this指针参数,所以绑定时传对象或者对象的指针过去都可以function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(pd, 1.1, 1.1) << endl;function<double(Plus&&, double, double)> f7 = &Plus::plusd;cout << f7(move(pd), 1.1, 1.1) << endl;cout << f7(Plus(), 1.1, 1.1) << endl;return 0;}
2.3 逆波兰表达式求值. - 力扣(LeetCode)
在学习function之前,我们可以使用switch语句来建立运算符与运算逻辑之间的关系:
// 传统方式的实现class Solution {public:int evalRPN(vector<string>& tokens) {stack<int> st;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之后,我们可以使用map来建立二者的关系:
// 使用map映射string和function的方式实现// 这种方式的最大优势之一是方便扩展,假设还有其他运算,我们增加map中的映射即可class Solution {public:int evalRPN(vector<string>& tokens) {stack<int> st;// function作为map的映射可调用对象的类型map<string, function<int(int, int)>> opFuncMap = {{"+", [](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; }}};for(auto & str : tokens){if (opFuncMap.count(str)) // 操作符{int right = st.top();st.pop();int left = st.top();st.pop();int ret = opFuncMap[str](left, right);st.push(ret);} else{st.push(stoi(str));}} return st.top();}};
之前无法使用这样的方式来做的原因是:各个lambda表达式(或其他可调用对象)的类型不一样,导致这些lambda表达式无法放到同一个map中进行管理。
可以说,function包装器的一大作用就是可以将返回值和参数列表相同的一类可调用对象的类型统一起来,方便传参或存储。
3. bind
std::bind也是C++标准库中的一个包装器,它的主要作用是将一个可调用对象的参数进行调整,生成并返回一个新的可调用对象。
// 不显示指定返回类型,编译器自动推导template <class Fn, class... Args>typename std::result_of<Fn(Args...)>::type bind(Fn&& fn, Args&&... args);// 显式指定返回类型template <class Ret, class Fn, class... Args>Ret bind(Fn&& fn, Args&&... args);
std::bind的主要作用有两个:
(1)指定某个参数的值并绑定到新的可调用对象。
(2)调整参数的顺序。
Args是一个占位符列表,用于在绑定时指定传递给可调用对象的参数。
这些占位符可以是实际的参数值,也可以是std::placeholders命名空间中定义的占位符,如std::placeholders::_1、std::placeholders::_2等,它们在后续的调用中会被实际传递的参数所替代。
3.1 调整参数顺序
利用std::placeholders命名空间中定义的占位符,我们可以对参数的顺序进行调整。
_1、_2、...、_n分别代表原函数中的第1个参数、第2个参数、...、第n个参数。
在bind的参数列表中按我们需要的顺序给出这些占位符,就可以实现参数顺序的调整:
#include<functional>#include<iostream>using namespace std;using placeholders::_1;using placeholders::_2;using placeholders::_3;int Sub(int a, int b){return (a - b) * 10;}int main(){ // bind 本质返回的一个仿函数对象// 调整参数顺序(不常用)// _1代表第一个实参// _2代表第二个实参// ... // 10->_1(对应a), 5->_2(对应b), 结果为50auto sub1 = bind(Sub, _1, _2);cout << sub1(10, 5) << endl; // 10->_2(对应b), 5->_1(对应a), 结果为-50auto sub2 = bind(Sub, _2, _1);cout << sub2(10, 5) << endl;return 0;}
3.2 绑定参数
当占位符是实际的参数值时,对应位置的参数的值会被绑定为该实际值:
#include<functional>#include<iostream>using namespace std;using placeholders::_1;using placeholders::_2;using placeholders::_3;int Sub(int a, int b){return (a - b) * 10;}int SubX(int a, int b, int c){return (a - b - c) * 10;}class Plus{ public:static int plusi(int a, int b){return a + b;} double plusd(double a, double b){return a + b;}};int main(){// 调整参数个数auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;// 成员函数对象进行绑定,就不需要每次都传递了function<double(Plus&&, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(move(pd), 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind一般用于绑定一些固定参数function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;// 计算复利的lambdaauto func1 = [](double rate, double money, int year)->double {double ret = money;for (int i = 0; i < year; i++){ret += ret * rate;} return ret - money;};// 绑定一些参数,实现出支持不同年华利率,不同⾦额和不同年份计算出复利的结算利息function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);function<double(double)> func10_2_5 = bind(func1, 0.025, _1, 10);function<double(double)> func20_3_5 = bind(func1, 0.035, _1, 30);cout << func3_1_5(1000000) << endl;cout << func5_1_5(1000000) << endl;cout << func10_2_5(1000000) << endl;cout << func20_3_5(1000000) << endl;return 0;}
注意:某参数被绑定之后,占位符的对应关系中不再考虑该参数,而是对剩下的参数依次编号为_1、_2、...、_n-1,多个参数被绑定时同理。