C++11 包装器

前言

我们目前已经介绍了三种可调用的对象,1、函数指针 2、仿函数 3、lambda表达式;但是这三都是不同的类型,无法做到调用时的类型的统一!如何使得相同返回值参数调用对象以相同的方式调用呢?这就是我们介绍的包装器

目录

前言

一、function

包装可调用函数

包装成员函数

一些参数包的问题

function的一些使用场景

二、bind

bind的参数包

bind的使用


一、function

function包装器,又称适配器。和我们当时STL栈和队列的那个适配器一样!C++中的function本质是一个类模板

我们先来看看,没有包装器存在的问题:

template<class F, class T>
T Func(F f, T x)
{
	static int cnt = 0;
	cout << "cnt: " << ++cnt << endl;
	cout << "&cnt: " << &cnt << endl;
	return f(x);
}

// 函数指针
int f(int d)
{
	return d + 2;
}

// 仿函数
struct Functor
{
	int operator()(int i)
	{
		return i + 2;
	}
};

int main()
{
	// 函数指针实例化
	cout << Func(f, 3) << endl << endl;
	// 仿函数实例化
	cout << Func(Functor(), 3) << endl << endl;
	// lambda
	cout << Func([](int i) {return i + 2; },  3);
	return 0;
}

这里它实际上是将上面的模板实例化了三份:

这三个函数的参数,返回值都一样,只是他们的类型不一样,这就导致了,调用时实例了三份(静态变量有三份,地址不同)!如果他们的类型是一样的,则模板只需要实例一份即可!这就大大的提高了使用模板的效率!如何实现呢?当然是使用function包装喽:

包装可调用函数

我们先来介绍一下function的原型, 他在<functional>的头文件中:

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

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

OK,有了function我们就可以对上述的,参数和返回值都一致,但是类型不一致的三个函数进行统一封装成,相同的类型:

int main()
{
	// 函数指针实例化
	function<int(int)> f1 = f;
	cout << Func(f1, 3) << endl << endl;

	// 仿函数实例化
	function<int(int)> f2 = Functor();
	cout << Func(f2, 3) << endl << endl;

	// lambda
	function<int(int)> f3 = [](int i) {return i + 2; };
	cout << Func(f3, 3) << endl;

	return 0;
}

这里我们明显的看到他们的静态变量只有一份,且地址都是同一个!上面我们知道他们不是统一的类型,但是这里如何验证他们的类型一致呢?其实很简单,我们只需要使用typeid即可:

包装成员函数

function不仅可以包装,函数指针、函数对象(仿函数)、lambda;还可以包装成员函数:

成员函数又分为两种:1、静态成员函数  2、非静态成员函数

静态的包装就很简单了:

• 静态成员函数没有this指针,可以和上面的普通函数指针一样用,但注意的是需要指定类域

它的包装格式如下:

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	int plusd(int a, int b)
	{
		return a + b;
	}
};


int main()
{
	// 包装静态成员函数
	function<int(int, int)> f = Plus::plusi;
	// function<int(int, int)> f = &Plus::plusi; // 静态这里有无&都可以
	cout << f(3, 5) << endl;

	return 0;
}

• 非静态的成员函数包装稍有点复杂,因为非静态成员的函数需要对象/对象的指针调用,所以得把对象/对象的指针传递过去,注意:这里也是需要指定类域的,且非静态成员函数必须加&

非静态成员的包装格式如下:

// 包装非静态的成员函数
function<int(Plus*, int, int)> f2 = &Plus::plusd;// 传递对象的指针
Plus plus;
cout << f2(&plus,3, 5) << endl; 
// cout << f2(Plus(),3, 5) << endl; // error不能传递匿名对象

function<int(Plus, int, int)> f3 = &Plus::plusd;// 传递对象
cout << f3(plus, 3, 5) << endl;
cout << f3(Plus(), 3, 5) << endl;

一些参数包的问题

• 可不可以将对象/对象的指针设置为引用?

不可以的!首先你设置左值引用的话匿名对象就引用不了了

• 设置成右值引用,左值就无法传递了!

• 设置成const 左值引用呢?

不行,这样写直接无法包装了!也就是无法构造function对象了!

• 那可不可以给搞成万能引用呢?

这个更不行,万能引用/易用折叠是函数模板的概念!

function的一些使用场景

150.逆波兰表达式

这个题目我们以前是写过的,主要思想就是用栈!这里先回忆一下,以前的两种写法:

1、暴力判断+栈

思路:遍历字符串,如果是运算符,获取栈顶的操作数两次,然后计算;否则就是操作数,入栈!最后栈里面的那个元素就是最后的答案!

class Solution 
{
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        for(const auto& c : tokens)
        {
            // 运算符计算
            if(c == "+" || c == "-" || c == "*" || c == "/")
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                if(c == "+") st.push(left + right); 
                else if(c == "-")  st.push(left - right); 
                else if(c == "*")  st.push(left * right); 
                else if(c == "/")  st.push(left / right); 
            }
            else // 数字入栈
            {
                st.push(stoi(c));
            }
        }

        return st.top();
    }
};

过了,但是这样呢写代码,属实不优雅!我们可以使用今天的包装器,将要执行的方法对应的函数提前处理好:

class Solution 
{
public:
    int evalRPN(vector<string>& tokens) 
    {
        stack<int> st;
        // 提前将所执行的方法和操作符建立映射
        map<string, function<int(int,int)>> m = {
            {"+", [](int left, int right){return left + right;}}, 
            {"-", [](int left, int right){return left - right;}},
            {"*", [](int left, int right){return left * right;}},
            {"/", [](int left, int right){return left / right;}},
        };

        for(const auto& c : tokens)
        {
            // 运算符计算
            if(m.count(c))
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                st.push(m[c](left, right));
            }
            else // 数字入栈
            {
                st.push(stoi(c));
            }
        }

        return st.top();
    }
};

可能这样写你觉得还不是和上面的也差不多嘛!但是你有没有想过,万一不是只有四个操作符,后还会增加呢?下面的写法就是把对应的操作符加上即可,上面的可就是一堆的暴力判断了!代码的鲁棒性(健壮性)明显提升了!

当然,function不知是在OJ中的使用,在十几种也有很多的使用场景,比如:即可哈希表/map与用户输入的指令,与对应方法的完美结合:

#include <unordered_map>
#include <string>

int main()
{
	// 包装了返回值为 void,参数为 void 的函数类型
	unordered_map<string, function<void(void)>> hash;

	hash["下载请求"] = []()->void { cout << "正在进行下载任务..." << endl; };
	hash["SQL查询"] = []()->void { cout << "正在进行SQL查询..." << endl; };
	hash["日志记录"] = []()->void { cout << "正在记录日志信息..." << endl; };

	string comm; // 指令
	while (cin >> comm)
	{
		if (!hash.count(comm))
			cout << "该指令不存在,请重新输入" << endl;
		else
			hash[comm](); // 调用函数
	}

	return 0;
}

这种设计在往网络中非常常见!也就是function是非常常用的!


二、bind

bind是一个函数模板,他就像一个函数包装器(适配器),接受一个可调用对象生成一个新的可调用对象来"适应"原对象的参数列表!

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&& fn就是他接受的要包装/适配的目标函数Args&&... args是生成的可调用对象的参数列表!其中,这是是函数模板,所以&&都是万能引用

bind的参数包

bind的参数包,是一个个的“整数”,其实他是一种占位符,通常是在 placeholders 的命名空间中!其中_1永远代表第一个bind的第一个参数;_n始终代表bind的第n个参数!


bind的使用

OK,我们还是用一下,首先使用一下改变原来参数的顺序

int sub(int x, int y)
{
	return x - y;
}

int main()
{
	// 未改变原来参数的顺序
	auto bfunc1 = bind(sub, placeholders::_1, placeholders::_2);
	cout << bfunc1(5, 3) << endl;

	// 改变原来参数的顺序
	auto bfunc2 = bind(sub, placeholders::_2, placeholders::_1);
	cout << bfunc2(5, 3) << endl;
	return 0;
}

这里实现改变顺序的原因很简单,就是前面说的bind的参数是_1是默认只接受第一个参数,它的内部其实也是和上面的function一样实现了仿函数,再去拿着bind参数列表中的参数去调用目标函数了!这里由于bind中的_1 和 _2调换了,所以调目标函数的时候也就传反了!即实现了参数的顺序调整!

OK,这里的一般的对象也绑定了(仿函数和lambda同理),可不可以绑定成员函数有呢?当然是可以的:

class Test
{
public:
	Test(int x) :_n (x){}

	int sub(int x, int y)
	{
		return x - y;
	}
private:
	int _n;
};


int main()
{
	// 未改变原来参数的顺序
	auto bfunc1 = bind(&Test::sub, placeholders::_1, placeholders::_2, placeholders::_3);
	Test test(1);
	cout << bfunc1(&test, 5, 3) << endl;

	// 改变原来参数的顺序
	auto bfunc2 = bind(&Test::sub, placeholders::_1, placeholders::_3, placeholders::_2);
	//Test test(2);
	cout << bfunc2(Test(1), 5, 3) << endl;
	return 0;
}

注意这里一旦要绑定成员函数,就必须得传递对象的地址/对象还是和上面的function一样成员函数是由对象/对象的指针调用的由于要传递对象/对象的指针,所以第一个参数就是对象/对象的地址!

当然其实改变参数的顺序其实是具体没啥意义的!下面我们来看看改变参数的个数:

上面刚写的,这个绑定成员函数的,让人很不爽的一点就是得手动的传递对象/对象的指针,我不想传递,可不可以呢?当然是可以的,此时只需要将_1与一个对象/地址绑定死即可:

// 未改变原来参数的顺序
auto bfunc1 = bind(&Test::sub, &Test(1), placeholders::_1, placeholders::_2);
cout << bfunc1(5, 3) << endl;

// 改变原来参数的顺序
auto bfunc2 = bind(&Test::sub, Test(1), placeholders::_2 ,placeholders::_1);
cout << bfunc2(5, 3) << endl;

这样调用的时候就非常的爽了!如果你想后面的参数绑定死也是可以的:

当然你直接在bind把参数全部传递过去了,下面再调用就不在有效果了!

除了上述的,用auto推导类型以外,还可用function

这里可以理解为又在bind上包装了一层!


OK,本期分享就到这里!我是cp我们下期再见~!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值