【C++11】C++11——包装器
迪丽瓦拉
2025-06-01 14:04:52
0

文章目录

    • function包装器
      • 1.function包装器概念
      • 2.function包装器统一类型
      • 3.function包装器优化代码
    • bind包装器
      • 1.bind包装器概念
      • 2.bind包装器绑定固定参数
      • 3.bind包装器调整传参顺序

function包装器

1.function包装器概念

function包装器也叫做适配器,C++11中的function本质是一个类模板,也是一个包装器。

其实这些都是可调用对象:C语言的函数指针、仿函数/函数对象、lambda表达式、今天说的是包装器,主要是function包装器与bind包装器。

类模板原型:

template  function;   
template 
class function;

模板参数说明:Ret:被包装的可调用对象的返回值类型。Args...:被包装的可调用对象的形参类型。

废话不多说,我们先来简单地使用一下是把,分别包装函数指针、仿函数、Lambda表达式、成员函数:

包装函数指针、仿函数、Lambda表达式:

#include 
int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator()(int a, int b){return a + b;}
};int main()
{//1.包装函数指针function f1;f1 = f;cout << f1(10, 20) << endl;function f2(f);cout << f2(10, 20) << endl;//2.包装仿函数/*Functor f1();function f3(f1);*/function f3 = Functor();cout << f3(10, 20) << endl;//3.包装Lambda表达式function f4 = [](const int a, const int b) {return a + b; };cout << f4(10, 20) << endl;return 0;
}

注意头文件#include

包装成员函数:(成员函数分为静态和非静态,本质还是函数指针)

class P
{
public:static int P1(int a, int b){return a + b;}int P2(int a, int d){return a + d;}
};
int main()
{	//静态成员函数function f5 = &P::P1;//成员函数要加上类域限制cout << f5(10, 20) << endl;//非静态成员函数,this指针不允许显式传递function f6 = &P::P2;cout << f6(P(), 10, 20) << endl;
}

取静态成员函数的地址可以不用"&",但是取非静态成员函数的地址就必须带上"&"

包装非静态成员函数是需要注意:非静态成员函数的第一个参数是隐藏this指针,所以在包装的时候需要指明第一个形参的类型为类的类型

包装器本质就是对各种可调用对象进行类型的统一。

2.function包装器统一类型

我们提供一个函数模板useF:

传入该函数模板的第一个参数可以是任意的可调用对象:如我们上面所说的函数指针、仿函数、lambda表达式等。

useF中定义了静态变量count,每次调用时将count的值和地址进行了打印,可判断多次调用时调用的是否是同一个useF函数。

函数模板useF代码:

template
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}

现在我们传入第二个参数相同的类型,但是传入的可调用对象的类型是不同的,那么在编译阶段该函数模板就会被实例化多次:

struct Functor
{double operator()(double d){return d / 3;}
};double f(double i)
{return i / 2;
}
int main()
{//函数名cout << useF(f, 9.9) << endl;//函数对象cout << useF(Functor(), 9.9) << endl;//lambda表达式cout << useF([](double d)->double {return d / 3; },9.9) << endl;return 0;
}

image-20230322181857536

由于函数指针、仿函数、lambda表达式是不同的类型,那么函数模板useF也会实例化出三份,结果打印出来也是不同的。

但是如果有需求:不实例化出三份,因为虽然三次调用传入可调用对象类型不同,但是调用对象的返回值与形参类型相同

此时我们就可以使用包装器对这三个不同的调用对象进行包装了,分别包装这三个可调用对象来调用useF函数,这时候就值实例化出一份useF函数。

#include 
template
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
struct Functor
{double operator()(double d){return d / 3;}
};
double f(double i)
{return i / 2;
}
int main()
{//函数名function f1 = f;cout << useF(f1, 9.9) << endl;//函数对象function f2 = Functor();cout << useF(f2, 9.9) << endl;//lambda表达式function f3 = [](double d)->double {return d / 4; };cout << useF(f3,9.9) << endl;return 0;
}

这时三次调用useF函数所打印count的地址就是相同的,并且count在三次调用后会被累加到3,表示这一个useF函数被调用了三次。

3.function包装器优化代码

还记得我们写过的题目:逆波兰表达式求值吗?

解题过程:

定义一个栈,遍历字符串,如果是数字直接入栈(字符串转化成数字stoi),如果遍历到的字符是加减乘除运算符,则从栈顶取出两个数字,进行相关的运算,最后在将结果入栈。遍历完字符串之后,栈顶的数字就是最终的计算结果

class Solution {
public:int evalRPN(vector& tokens) {stack st;for(int i = 0;i

对于上面的代码,通过多个if语句判断进行哪一个运算,如果运行增加,那么我们就需要继续添加if判断语句了。

这时候,我们可以利用function包装器来优化上面的代码:

建立各个运算符与其对应需要执行的函数间的映射关系,当执行某一个运算时就可以直接通过运算符找到对应的函数执行;

当运算符类型增加时,我们只需要建立新增运算符与其对应函数间的映射关系即可。

class Solution {
public:int evalRPN(vector& tokens) {stack st;//命令与动作的映射map> opFuncMap = {{"+",[](int x,int y)->int{return x+y;}},{"-",[](int x,int y)->int{return x-y;}},{"*",[](int x,int y)->int{return x*y;}},{"/",[](int x,int y)->int{return x/y;}},};for(auto&e:tokens){//操作数进栈if(opFuncMap.count(e)==0){st.push(stoi(e));}//操作符else{int right = st.top();st.pop();int left = st.top();st.pop();st.push(opFuncMap[e](left,right));}}return st.top();}
};

function包装器可以将可调用对象的类型进行统一,便于我们对其进行统一化;包装后明确8可调用对象的返回值和形参类型,更加方便使用者使用。


bind包装器

1.bind包装器概念

bind函数定义在头文件中,也是一个函数模板,就像一个函数包装器(适配器),接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

bind函数模板原型:

template 
/* unspecified */ bind(Fn&& fn, Args&&... args);
template 
/* unspecified */ bind(Fn&& fn, Args&&... args);

参数说明:fn:可调用的对象,…args:要绑定的参数列表。

2.bind包装器绑定固定参数

bind绑定可以与function结合,placeholders:命名空间,_1,_2:占位对象

  • 无意义绑定
int Plus(int a, int b)
{return a + b;
}
int main()
{//表示绑定函数plus参数分别由调用func1的第一,二个参数指定std::function func1 = std::bind(Plus, placeholders::_1, placeholders::_2);cout << func1(10, 20) << endl;return 0;
}

第一个参数传入函数指针,后面传入绑定的参数列表依次是placeholders::_1和placeholders::_2,表示后续调用新生成的可调用对象时,第一个参数传给placeholders::_1,第二个参数传给placeholders::_2。此时绑定后生成的新的可调用对象的传参方式,和原来没有绑定的可调用对象是一样的,所以说这是一个无意义的绑定。

  • 固定绑定参数

参数是可以进行固定绑定的:比如我们把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为20:

#include 
int Plus(int a, int b)
{return a + b;
}
int main()
{function func = bind(Plus, placeholders::_1,20);cout << func(2) << endl;//22return 0;
}

3.bind包装器调整传参顺序

我们以Sub类为例子,对于下面代码中的Sub类的成员函数sub,第一个参数是this指针,所以如果想要调用sub时不用对象进行调用,那么我们就可以通过上面所说的固定绑定参数,把sub成员函数的第一个参数固定绑定为Sub对象:

class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{function func4 = &Sub::sub;cout << func4(Sub(), 10, 20) << endl;//-10//绑定固定参数,不需要传Sub()function func5 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout << func5(10,20) << endl;//-10return 0;
}

此时调用绑定后生成的可调用对象时,就只需要传入用于相减的两个参数了,因为在调用时会固定帮我们传入一个匿名对象给this指针.

bind包装器的传参是可以进行调整的,将sub成员函数用于相减的两个参数的顺序交换,那么直接在绑定时将placeholders::_1和placeholders::_2的位置交换一下就行了:

class Sub
{
public:int sub(int a, int b){return a - b;}
};
int main()
{function func5 = bind(&Sub::sub, Sub(), placeholders::_2, placeholders::_1);cout << func5(10,20) << endl;//10return 0;
}

bind包装器可以将一个函数的某些参数绑定为固定值,让我们在调用时可以不传递某些参数,也可以对函数参数的顺序进行调整

相关内容