【C++11】包装器

function包装器

function的介绍

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

function类模板的定义如下:

cpp 复制代码
Func();

上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能 是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下! 为什么呢?我们继续往下看

cpp 复制代码
template<class F, class T>
T useF(F f, T n)
{
	static int count = 0;
	cout << "count: " << ++count << endl;
	cout << "&count: " << &count << endl;

	return f(n);
}

double f(double x)
{
	return x;
}

struct Functor
{
	double operator()(double i)
	{
		return i;
	}
};

int main()
{
	//使用函数指针
	useF(f, 10.0);

	//使用仿函数
	useF(Functor(), 20.0);

	//使用lambda表达式
	useF([](double a)->double{return a; }, 30.0);

	return 0;
}

运行测试:

由于函数指针,仿函数,lambda表达式是不同类型,因此useF模板类在编译阶段被实例化成了三份,开辟了三个不同栈帧

这里实际上没有必要实例化出三份useF函数,因为三次调用useF函数时传入的可调用对象虽然是不同类型的,但这三个可调用对象的返回值和形参类型都是相同的

为了解决上述问题,C++引入了包装器 function

function包装器同一类型

使用function需要包含functional头文件

类模板原型如下:

cpp 复制代码
template <class T> function;     // undefined
 template <class Ret, class... Args>
 class function<Ret(Args...)>;

模板参数说明:
Ret: 被调用函数的返回类型
Args...:被调用函数的形参

function包装器可以对可调用的对象进行包装,包括函数指针(函数名),仿函数(函数对象),lambda表示式,类的成员函数(包括静态成员函数)。比如:

cpp 复制代码
// 使用方法如下:
#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()
{
	// 函数名(函数指针)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;

	// 函数对象
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;

	// lamber表达式
	std::function<int(int, int)> func3 = [](const int a, const int b)
		{return a + b; };
	cout << func3(1, 2) << endl;

	// 类的非静态成员函数
	std::function<int(int, int)> func4 = &Plus::plusi;
	cout << func4(1, 2) << endl;
	
	// 类的静态成员函数
	std::function<double(Plus, double, double)> func5 = &Plus::plusd;
	cout << func5(Plus(), 1.1, 2.2) << endl;

	return 0;
}

对以上代码的说明:

包装时指明返回值类型和各形参类型,然后将可调用对象赋值给function包装器即可,包装后function对象就可以向函数一样使用

取非静态成员函数的地址必须使用取地址运算符(&),但是取静态成员的地址可以不使用取地址运算符(&)。建议不管是静态还是非静态的成员函数,最好都加上取地址运算符(&)

包装非静态的成员函数是需要注意,非静态成员函数的第一个参数是隐藏的this指针,因此在包装时需要指明第一个形参的类型为类的类型,在使用时也需要也需要显示的传this指针

function简化代码

使用包装器解决上述实例化多份问题

使用包装器分别对着三个可调用对象进行包装,然后再用这三个包装后的可调用对象来调用useF函数,这时就只会实例化出一份useF函数

cpp 复制代码
template<class F, class T>
T Func(F f, T n)
{
	static int count = 0;
	cout << "count: " << ++count << endl;
	cout << "&count: " << &count << endl;

	return f(n);
}

int f(int a)
{
	return a;
}

struct Functor
{
public:
	int operator() (int a)
	{
		return a;
	}
};

int main()
{
	// 函数名(函数指针)
	std::function<int(int)> func1 = f;

	// 函数对象
	std::function<int(int)> func2 = Functor();

	// lamber表达式
	std::function<int(int)> func3 = [](const int a)
		{return a; };

    //三个函数被包装器包装,类型都是function<int(int)>
	vector<function<int(int)>> f = {func1, func2, func3};

	int x = 10;
	for (auto F : f)
	{
		Func(F, x);
	}

    return 0;
}

运行测试:

可以看到使用包装器之后,现在调用Func时并没有开辟三个栈帧

**根本原因:**这三个函数起被包装后,类型得到了统一,都是function类型,所以它们只会开辟一个函数栈帧

我们可以使用包装器对逆波兰表达式进行优化

先看原先的代码:

cpp 复制代码
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();
    }

    
};

使用包装器修改之后:

cpp 复制代码
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string, function<int(int, int)>> f = {
            {"+", [](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(f.count(str))
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                st.push(f[str](left, right)); //注意:左右操作数
            }
            else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }   
};

function包装器的意义:

将可调用的对象的类型进行统一,便于我们进行统一化管理

包装之后明确了返回值类型和形参类型,更加方便使用者使用

bind(绑定)

bind的介绍

bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来"适应"原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用bind函数还可以实现参数顺序调整等操作。

原型如下:

cpp 复制代码
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... : 要绑定的参数列表:值或占位符

调用bind的一般形式

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

callable:需要包装的可调用对象

newCallable:一个新的可调用对象

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

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是"占位符",表示 newCallable的参数,它们占据了传递给newCallable的参数的"位置"。数值n表示生成的可调用对 象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

此外,除了用auto接好包装后的可调用对象,也可以使用function接收包装好的可调用对象

bind函数无意义绑定

cpp 复制代码
int Mul(int x, int y)
{
	return x - y;
}

int main()
{
    //无意义的绑定
	function<int(int, int)> f = bind(Mul, placeholders::_1, placeholders::_2);
    cout<<f(10, 20);    

    return 0;
}

placeholders::_1绑定的第一个参数,placeholders::_2绑定的第二个参数。和原来没有绑定的1可调用顺序是一样的,所以这种绑定是无意义

绑定固定参数

如果想把Plus函数的第二个参数固定绑定为10,可以在绑定时将参数列表的placeholders::_2设置为10。

比如:

cpp 复制代码
int sub(int x, int y)
{
	return x - y;
}

int main()
{
	function<int(int)> f = bind(sub, placeholders::_1, 30);
    cout << f(10) << endl;
 
    return 0;
}

此时调用绑定后新生成的可调用对象时就只需要传入一个参数,它会将该值与10相加后的结果进行返回

调整传参顺序

如果向交换两个参数的相减的顺序,那么我们只需要将placeholders::_1和placeholders::_2的位置交换一下就可以了

cpp 复制代码
int sub(int x, int y)
{
	return x - y;
}

int main()
{
	function<int(int, int)> f = bind(&Plus::plusi, placeholders::_2, placeholders::_1);

	cout << f(10, 30) << endl;
    return 0;
}

原本输出是-20,现在输出为:20

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

bind绑定的意义

将第一个函数的某些参数绑定为固定的值,让我们再调用时可以不用传递某系参数

可以对函数参数的顺序进行灵活的调整

相关推荐
hakesashou16 分钟前
python如何打乱list
开发语言·python
网络风云32 分钟前
【魅力golang】之-反射
开发语言·后端·golang
Want5951 小时前
Java圣诞树
开发语言·python·信息可视化
运维小文1 小时前
python之打印、变量、格式化输出
开发语言·python·python基础·hello world
坐井观老天1 小时前
使用 C# 测量程序运行消耗的的时间
开发语言·c#
小哈龙2 小时前
c++ 类似与c# 线程 AutoResetEvent 和 ManualResetEvent的实现
c++·c#·多线程
IT猿手2 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解LRMOP1-LRMOP6及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·算法·matlab·智能优化算法·多目标算法
YYY_小后知2 小时前
C# 中 Webclient和Httpclient
开发语言·c#
kittygilr2 小时前
matlab中的cell
开发语言·数据结构·matlab
yuanbenshidiaos2 小时前
C++--------------树
java·数据库·c++