【C++11】包装器和bind

本文介绍了C++中包装器(如function和bind)的概念,为何需要包装器解决可调用对象类型差异问题,展示了包装器如何统一函数、仿函数对象和lambda表达式的类型,并展示了bind函数模板在设定默认参数值和调整传参顺序的应用场景。
摘要由CSDN通过智能技术生成

一. 为什么要有包装器?

function 包装器,也叫作适配器。C++ 中的 function 本质上是一个类模板,也是一个包装器。接下来我们来看看,C++ 为什么引入 function 呢?

在 C++ 中,可调用对象有以下三种:函数名/函数指针、仿函数对象、lambda 表达式

// 加法普通函数
int NormalPlus(int num1, int num2)
{
	return num1 + num2;
}

// 加法仿函数
class FunctorPlus
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

int main()
{
	// 加法 lambda 表达式
	auto LambdaPlus = [](int num1, int num2) {return num1 + num2; };

	// 1、调用普通函数
	NormalPlus(10, 20);
	// 2、调用仿函数对象
	FunctorPlus obj;
	obj(10, 20);
	// 3、调用 lambda 表达式
	LambdaPlus(10, 20);
}

可以看到,它们的调用方式可以不能说相似,只能说是一模一样,且它们函数体中执行的内容都是完全相同的;但是它们彼此之间的类型不同,不能进行相互赋值等操作。
在这里插入图片描述

上面是三种不同的可调用对象,那么它们的类型不同还可以理解;但是在 lambda 表达式中,就算功能相似的 lambda 表达式,它们直接的类型也互不相同:
在这里插入图片描述

这就导致在很多应用场景中,lambda 表达式使用起来是非常不便的。比如之前我们大量进行回调函数时,会采用函数指针的方式,构建一个函数指针数组,调用时按其对应的下标调用即可。但是 lambda 表达式则不然,每一个 lambda 表达式的类型不相同,我们没办法去开辟一个定类型的数组,也就意味着传统的函数指针的方式是不可行的。

包装器的诞生就是为了解决这个问题,通过包装器,可以让功能相似的可调用对象(函数名/函数指针、仿函数对象和 lambda 表达式)的类型统一,使其可以相互联系,相互转化。

二. 什么是包装器?

包装器是个类模板,它的定义在头文件 functional 中
在这里插入图片描述

在这里插入图片描述

下面看看具体的示例:
在这里插入图片描述

也就是说,在 function 的模板列表中,第一个参数是返回值的类型,随后在括号里依次传入形参的类型;而后在赋值的时候,只需要让其等于已经定义过的可调用对象,这样就算包装完成了,最后我们可以使用这个包装好的对象来代替之前的可调用对象,去执行它们的功能。

三. 包装器的使用

还是刚刚的例子,我们分别包函数名/函数指针,仿函数对象,lambda 表达式,看看包装完之后它们各自的类型是什么

// 加法普通函数
int NormalPlus(int num1, int num2)
{
	return num1 + num2;
}

// 加法仿函数
class FunctorPlus
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

int main()
{
	// 加法 lambda 表达式
	auto LambdaPlus = [](int num1, int num2)->int{return num1 + num2; };

	// 使用包装器包装函数名
	function<int(int, int)> function1 = NormalPlus;

	// 使用包装器包装函数地址
	function<int(int, int)> function2 = &NormalPlus;

	// 使用包装器包装仿函数对象(这里的 FunctorPlus() 是一个匿名的仿函数对象)
	function<int(int, int)> function3 = FunctorPlus(); 

	// 使用包装器包装 lambda 表达式
	function<int(int, int)> function4 = LambdaPlus;

	// 其类型都为 class std::function<int __cdecl(int,int)>
	cout << typeid(function1).name() << endl;
	cout << typeid(function2).name() << endl;
	cout << typeid(function3).name() << endl;
	cout << typeid(function4).name() << endl;

	// 它们之间也可以任意赋值
	function1 = function3;
	function3 = function4;

	return 0;
}

------运行结果-------
class std::function<int __cdecl(int,int)>
class std::function<int __cdecl(int,int)>
class std::function<int __cdecl(int,int)>
class std::function<int __cdecl(int,int)>

可以发现,这几个可调用对象无论最开始它们是什么类型,最后都被包装器给包装成了 function<int(int, int)> 的类型。既然类型相同了,它们之间也就可以进行:互相赋值、共同存储在一个数组中的操作了。

通过包装器,我们可以轻轻松松实现函数回调:

int main()
{
	//实现一个计算器
	map<string, function<int(int, int)>> calculator =
	{
		{"加法",[](int a,int b) {return a + b; }},
		{"减法",[](int a,int b) {return a - b; }},
		{"乘法",[](int a,int b) {return a * b; }},
		{"除法",[](int a,int b) {return a / b; }}
	};

	cout << calculator["加法"](10, 20) << endl;
	cout << calculator["减法"](10, 20) << endl;
}

------运行结果-------
30
-10

如果没有包装器,那么只能采用普通函数构建函数指针数组,这既会产生大量命名冲突的风险,又会导致程序的简洁性大大降低。这里如果使用包装器去包装的话,便把 lambda 表达式简洁易读的特点放到了最大。

四. bind 函数模板

1. 为什么要有 bind ?

前面一直说的是普通函数,别忘了还有类中的成员函数,那包装器如何包装和调用成员函数呢?

class A
{
public:
	// 普通成员函数
	int Plus(int num1, int num2)
	{
		return num1 + num2;
	}
	// 静态成员函数
	static int PlusStatic(int num1, int num2)
	{
		return num1 + num2;
	}
};

int main()
{
	// 静态成员函数(没有this指针,其实和普通函数没什么区别,函数名和函数地址相同)
	function<int(int, int)> funcStatic1 = A::PlusStatic;
	function<int(int, int)> funcStatic2 = &A::PlusStatic;

	// 非静态成员函数(有this指针,this其实就是该类的对象,位于在参数列表中第一个参数位置)
	// 包装非静态成员函数时,不能用函数名,必须使用函数地址;对非静态成员函数而言,函数名和函数地址不同)
	function<int(A, int, int)> func1 = &A::Plus;

	// 调用包装器对象
	cout << funcStatic1(10, 20) << endl;
	cout << funcStatic2(10, 20) << endl;
	cout << func1(A(), 10, 20) << endl; // 非静态成员函数需要传入一个对象,然后通过这个对象去调用

	return 0;
}

------运行结果-------
30
30
30

总结一下关于成员函数的包装:

  • 类的静态成员函数和普通函数性质一样,它们的函数名和函数地址等价;但是非静态成员函数的地址则必须使用取地址运算符“&”。
  • 包装非静态成员函数时需要增加一个参数(this 指针,this 其实就是一个实例化的类对象),因为非静态成员函数需要用对象去调用,且非静态成员函数的第一个参数是隐藏 this 指针,因此在包装时需要指明第一个形参的类型为类的名称。
  • 静态成员函数因为没有 this 指针,所以它本质上其实和普通函数一样。
  • 调用包装好的非静态成员函数时,注意在参数列表中第一个参数位置传入该类的一个实例化对象,通常都是传的都是匿名对象。

为了让包装好的非静态成员函数使用起来更简单(不想每次使用时都要传入一个实例化对象), C++11 增加了新特性:bind 模板

2. 什么是 bind ?

std::bind 函数定义在头文件 functional 中,是一个函数模板,它也有点像上面的包装器(适配器),接受一个可调用对象(函数/函数名、仿函数对象、lambda 表达式),然后生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收 N 个参数的可调用对象 Func,通过绑定一些参数,返回一个接收 M 个(通常 M <= N)参数的新函数。另外,使用 std::bind 模板还可以修改参数的传参顺序。

具体说的话,bind 可以去给可调用对象(通常是静态成员函数)参数列表中的参数指定缺省值,或者更改形参的接收顺序,然后生成一个新的可调用对象来“适应”原对象的参数列表。

下面是 bind 的定义:
在这里插入图片描述

具体示例:
在这里插入图片描述

在 bind 的第一个参数中,我们输入被绑定的可调用对象的名称,后面再依次输入传参进来的参数的顺序。

3. bind 的使用场景

在绑定时,参数列表中参数的个数和顺序我们可以进行一些小调整:

作用一:给参数设定缺省值
在这里插入图片描述

作用二:调整传参顺序

int normal_plus(int num1, int num2)
{
	return num1 + num2;
}

int main()
{
	// 交换参数的顺序
	function<int(int, int)> fplus = bind
	(	
		normal_plus,
		placeholders::_2,//第一个参数传入 num2
		placeholders::_1 //第二个参数传入 num1
	);

	// 传入参数顺序为绑定的顺序
	fplus(10, 3); //3+10;
}
  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值