> 文章列表 > 【C++11】包装器

【C++11】包装器

【C++11】包装器

目录

一、function包装器

1.1 介绍

1.2 使用

1.3 function包装器的意义

1.3 function包装器的一个例子

二、bind包装器

2.1 介绍 

2.2 使用

2.3 bind包装器的意义


一、function包装器

1.1 介绍

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

function类模板的原型如下:

template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

 包装器使用了可变参数模板,模板参数介绍如下:

  • Ret:被调用函数的返回值类型
  • Args... :被调用函数的形参

代码解释如下:

  • 这段代码定义了一个模板类函数,该类接受一个或多个类型参数。
  • 第一行是一个不完整的声明,因为没有指定函数类型参数的具体类型,因此它是未定义的。
  • 第二行定义了一个模板类 function,它有一个类型参数Ret和一个变长模板参数Args,表示返回值类型和函数参数类型
  • 这个模板类的具体实现是针对函数类型 Ret(Args...)的。也就是说,这个模板类可以用来存储任意类型为Ret(Args..)的函数指针、函数对象等。

function包装器可以对可调用对象进行包装,包括函数指针(函数名)、仿函数(函数对象)、lambda表达式、类的成员函数等

使用 function包装器需要包含头文件:

#include <functional>

1.2 使用

对于以下函数模板useF:

  • 传入该函数模板的第一个参数可以是任意的可调用对象,比如函数指针、仿函数、lambda表达式等。
  • useF中定义了静态变量count,并在每次调用时将count的值和地址进行了打印,可判断多次调用时调用的是否是同一个useF函数
  • 在传入第二个参数类型相同的情况下,如果传入的可调用对象的类型是不同的,那么在编译阶段该函数模板就会被实例化多次

代码如下:

#include <iostream>
using namespace std;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;}
};
int main()
{// 函数名cout << useF(f, 11.11) << endl;cout << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;cout << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}

 运行结果,三者的 count 都是不同的,也就意味着该函数模板实例化了三份

由于函数指针、仿函数、lambda表达式是不同的类型,因此useF函数会被实例化出三份,三次调用useF函数所打印 count的地址也是不同的。但实际这里根本没有必要实例化出三份useF函数,因为三次调用 useF函数时传入的可调用对象虽然是不同类型的,但这三个可调用对象的返回值和形参类型都是相同的

这时就可以用包装器分别对着三个可调用对象进行包装,然后再用这三个包装后的可调用对象来调用useF函数,这时就只会实例化出一份useF函数。
根本原因就是因为包装后,这三个可调用对象都是相同的function类型,因此最终只会实例化出一份useF函数,该函数的第一个模板参数的类型就是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;}
};int main()
{//函数名function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;//函数对象function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;//lambda表达式function<double(double)> func3 = [](double d)->double {return d / 4; };cout << useF(func3, 11.11) << endl;return 0;
}

 运行结果,这时三次调用useF函数所打印 count的地址就是相同的,说明类模板实例化只生成了一份,并且count在三次调用后会被累加到3,表示 useF函数被调用了三次

function的包装器使用如下:

对于是函数、函数对象、lambda用法如下:

int f(int a, int b)
{return a + b;
}
struct Functor
{
public:int operator() (int a, int b){return a + b;}
};int main()
{// 函数名(函数指针)//int(int, int) 第一个int返回类型,(int, int)是参数列表function<int(int, int)> func1 = f;cout << func1(1, 2) << endl;//也可以这样function<int(int, int)> func2(f);cout << func2(1, 2) << endl;cout << endl;// 函数对象function<int(int, int)> func3 = Functor();cout << func3(1, 2) << endl;//这种匿名对象的写法,VS系列不支持,可能是编译器识别的问题/*function<int(int, int)> func4(Functor());cout << func4(1, 2) << endl;*///有名对象可以Functor ft;function<int(int, int)> func5 = ft;cout << func5(1, 2) << endl;cout << endl;// lambda表达式function<int(int, int)> func6 = [](const int a, const int b) {return a + b; };cout << func6(1, 2) << endl;return 0;
}

如果是类成员函数,使用有点区别

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 = &Plus::plusi;//类静态成员函数指针cout << func1(1, 2) << endl;//也可以不取地址function<int(int, int)> func2 = Plus::plusi;//类静态成员函数指针cout << func2(1, 3) << endl;//类普通成员函数//对于类普通成员函数,必须进行&,没有直接报错//参数列表还需要加上一个参数:类名,这个参数代表的是 this指针//this指针不能显示去传,所以需要用Plus替代function<double(Plus, double, double)> func8 = &Plus::plusd;//类普通成员函数指针//调用的时候必须要传多一个匿名对象,不传直接报错cout << func8(Plus(), 1.1, 2.2) << endl;return 0;
}

运行结果

function包装器其实就是对函数、函数对象、lambda表达式进行包装,包装的目的是: 将可调用对象的类型进行统一,但其本质上还是调用函数、函数对象、lambda表达式

1.3 function包装器的意义

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

1.3 function包装器的一个例子

链接:逆波兰表达式

 

  • 定义一个栈,依次遍历所给字符串。
  • 如果遍历到的字符串是数字则直接入栈。
  • 如果遍历到的字符串是加减乘除运算符,则从栈定抛出两个数字进行对应的运算,并将运算后得到的结果压入栈中。
  • 所给字符串遍历完毕后,栈顶的数字就是逆波兰表达式的计算结果

没有使用 function包装器,只能暴力判断,代码如下:

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();//暴力判断if(str == "+")  st.push(left + right);if(str == "-")  st.push(left - right);if(str == "*")  st.push(left * right);if(str == "/")  st.push(left / right);}else{st.push(stoi(str));}}return st.top();}
};

这种情况可以用包装器来简化代码。

  • 建立各个运算符与其对应需要执行的函数之间的映射关系,当需要执行某一运算时就可以直接通过运算符找到对应的函数进行执行。
  • 当运算类型增加时,就只需要建立新增运算符与其对应函数之间的映射关系即可

代码如下:

class Solution {
public:int evalRPN(vector<string>& tokens) {stack<int> st;map<string, function<int(int, int)>> opFuncMap ={{ "+", [](int i, int j) {return i + j; } },{ "-", [](int i, int j) {return i - j; } },{ "*", [](int i, int j) {return i * j; } },{ "/", [](int i, int j) {return i / j; } }};for (auto& str : tokens){if (opFuncMap.find(str) != opFuncMap.end()){int right = st.top();st.pop();int left = st.top();st.pop();st.push(opFuncMap[str](left, right));}else{//stoi to_string C++11st.push(stoi(str));}}return st.top();}
};

二、bind包装器

2.1 介绍 

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

bind函数模板的原型如下:

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

模板参数说明:

  • fn:可调用对象
  • Args:参数列表
  • Ret:返回值类型
  • args...:要绑定的参数列表:值或占位符

代码解释:

  • 这是C++11标准库中的bind函数的两个重载版本。它们的作用是创建一个函数对象,该函数对象可以将给定的函数对象和参数绑定在一起,以便稍后调用该函数对象时自动传递这些参数。
  • 第一个重载版本的返回类型是未指定的,它将返回一个可调用对象,该对象可以调用 fn函数对象,并用Args参数列表调用它。
  • 第二个重载版本的返回类型是未指定的,它将返回一个可调用对象,该对象可以调用 fn函数对象,并用Args参数列表调用它,然后将结果转换为Ret类型并返回。

可以将 bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。
调用bind的一般形式:auto newCallable = bind(callable,arg_list)

  • 其中,newCallable本身是一个可调用对象,
  • arg_list是一个逗号分隔的参数列表,对应给定的 callable的参数。
  • 当我们调用newCallable时,newCallable会调用 callable,并传给它 arg_list 中的参数

arg_list中的参数可能包含形如 _n的名字,其中n是一个整数,这些参数是“占位符”,表示 newCallable的参数,它们占据了传递给newCallable的参数的 “位置”。

数值n表示生成的可调用对象中参数的位置,比如 _1为 newCallable的第一个参数,_2为第二个参数,以此类推

占位符说明:

placeholders_1 是一个占位符,placeholders其实是一个命名空间

 

 此外,除了用 auto接收包装后的可调用对象,也可以用 function类型指明返回值和形参类型后接收包装后的可调用对象

2.2 使用

bind包装器绑定固定参数

绑定固定参数,测试代码如下:

int Plus(int a, int b)
{return a + b;
}int main()
{//表示绑定函数Plus 参数分别由调用 f1 的第一,二个参数为指定auto f1 = bind(Plus, placeholders::_1, placeholders::_2);cout << f1(1, 2) << endl;cout << f1(2, 2) << endl;//表示绑定函数 Plus 的第一个参数,第二个参数为: 10, 20auto f2 = bind(Plus, 10, 20);cout << f2() << endl;//表示绑定函数 Plus 的第一个参数为指定,第二个参数固定为:20auto f3 = bind(Plus, placeholders::_1, 20);cout << f3(1) << endl;//可以用 function类型指明返回值和形参类型后接收 bind包装后的可调用对象//f4 的类型为 function<void(int, int, int)> 与 f1类型一样function<int(int, int)> f4 = bind(Plus, placeholders::_1,placeholders::_2);cout << f1(1, 2) << endl;cout << f1(2, 2) << endl;return 0;
}

运行结果

还有一种无意义的绑定,下面这种绑定就是无意义的绑定: 

int Plus(int a, int b)
{return a + b;
}
int main()
{//无意义的绑定function<int(int, int)> func = bind(Plus, placeholders::_1, placeholders::_2);cout << func(1, 2) << endl; //3return 0;
}

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

bind包装器调整传参顺序

测试代码:

int Sub(int a, int b)
{return a - b;
}int main()
{function<int(int, int)> f1 = bind(Sub, placeholders::_1, placeholders::_2);cout << f1(2, 1) << endl;//调整传参顺序,只需更改占位符的位置即可function<int(int, int)> f2 = bind(Sub, placeholders::_2, placeholders::_1);cout << f2(2, 1) << endl;return 0;
}

运行结果:

根本原因就是因为,后续调用新生成的可调用对象时,传入的第一个参数会传给placeholders::_1,传入的第二个参数会传给placeholders::_2,因此可以在绑定时通过控制 placeholders::_n的位置,来控制第n个参数的传递位置

对于 bind绑定的是类成员函数时:

class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{// 绑定类成员函数function<int(int, int)> f1 = std::bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout << f1(2, 1) << endl;return 0;
}

运行结果

对于Sub类中的sub成员函数,sub成员函数的第一个参数是隐藏的this指针,如果想要在调用 sub成员函数时不用对象进行调用,那么可以将 sub成员函数的第一个参数固定绑定为一个Sub的对象,上面绑定的是匿名对象

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

2.3 bind包装器的意义

  • 将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。
  • 可以对函数参数的顺序进行灵活调整

实际上,不怎么使用 bind包装器,function包装器使用是比较多一些

----------------我是分割线---------------

文章到这里就结束了,下一篇即将更新