C++11 --- function 包装器以及 bind 适配器

4 篇文章 0 订阅

序言

 当我们学 C 语言 时,当讨论到可调用对象时,第一时间会想到函数,或者是函数指针。但是经过 C++ 的学习,可调用对象可就多了,除了上述两个还包括仿函数,lambda 表达式,类成员函数等。怎么进行统一的管理呢?


1. function 包装器

1.1 function 的作用

 很多时候,我们的可调用对象可能大相径庭,但是他们的功能都是一样的,如下代码:

// 函数
int AddFunc(const int& left, const int& right)
{
	return left + right;
}

// 仿函数
struct Addobject
{
	int operator()(const int& left, const int& right)
	{
		return left + right;
	}
};

// lambda 表达式
auto AddLam = [](const int& left, const int& right) { return left + right; };

我们就可以使用 function 将功能相同,类型不同的可调用对象统一起来!并且在大多数场景下,function 都是可以平替掉 函数指针 的,因为他更加的灵活。

1.2 function 的使用

function 使用起来也十分的方便,我们首先来看他的声明:

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

在这里我们主要介绍第二种形式:

  • Ret:代表调用对象的返回值
  • class... Args:代表可调用对象的参数

我们现在就针对于可调用对象分别做使用演示:

1. 函数
// 函数
int AddFunc(const int& left, const int& right)
{
	return left + right;
}

int main()
{
	// 注意匹配返回值类型和参数类型
	std::function<int(int, int)> func1 = AddFunc;
	std::cout << func1(1, 2) << std::endl;
	
	return 0;
}

2. Lambda 表达式
int main()
{
	// lambda 表达式
	std::function<int(int, int)> func1 = [](const int& left, const int& right) { return left + right; };
	std::cout << func1(1, 2) << std::endl;

	return 0;
}

使用时直接使用 function 接受表达式返回的类型。

3. 仿函数
// 仿函数
struct Addobject
{
	int operator()(const int& left, const int& right)
	{
		return left + right;
	}
};

int main()
{
	std::function<int(int, int)> func1 = Addobject();
	std::cout << func1(1, 2) << std::endl;

	return 0;
}

4. 类成员函数

 这个就较为特殊了,类成员函数分为 普通成员函数,静态成员函数,在这里我们首先介绍前者。
 这里可就有坑了!我们先看看怎么入坑的,以及为什么有坑?
在这里插入图片描述

参数类型和数量看起来是匹配的,其实不然,大家别忘了一个隐含指针:
在这里插入图片描述

这样的话参数列表就和我们的 function 不匹配了,有什么好的解决方案吗?这里有两个:

// 方案一: 创建一个类的对象,传入指针
std::function<int(AddClass*, int, int)> func1 = &AddClass::AddFunc;
AddClass ac;
std::cout << func1(&ac, 1, 1) << std::endl;

可以看出来,这十分的不优雅,太挫了!并且和上面的 function 一比,参数都变了!

我们再来看一个比较优雅的:

// 方案二:使用 lambda 表达式作为中间中转一下
std::function<int(int, int)> func1 = [](const int &left, const int &right)
{
	return AddClass().AddFunc(left, right);
};

这个看起来就顺眼多了吧!

起始还有一个方案三,我们在介绍 bind 之后再介绍一下。

 现在我们再介绍 静态成员函数,这个就友好一点了,这是因为静态成员函数 不依赖于类的任何特定实例来执行其功能,因此不需要 this 指针来访问类的成员变量或成员函数。
 举个例子吧:

class AddClass
{
public:
	int AddFunc(const int& left, const int& right)
	{
		return left + right;
	}

	static int AddStatic(const int& left, const int& right)
	{
		return left + right;
	}
};

int main()
{
	std::function<int(int, int)> func1 = AddClass::AddStatic;
	std::cout << func1(1, 2) << std::endl;

	return 0;
}

5. 模板函数

 模板函数再被介绍之前,需要指定类型:

template<class T>
T AddTle(const T& left, const T& right)
{
	return left + right;
}


int main()
{
	std::function<int(int, int)> func1 = AddTle<int>;
	std::cout << func1(1, 2) << std::endl;

	return 0;
}

2. bind 适配器

2.1 bind 的作用

C++11 中的 std::bind 是一个功能强大的函数适配器,它用于 生成新的可调用实体(通常是函数对象)std::bind 可以 绑定函数调用的某些参数到特定的值,然后返回一个新的函数对象,这个新的函数对象在被调用时,会自动使用绑定的参数值,并接受其他未绑定的参数。
 我们在这里主要介绍他的两个主要功能 顺序互换 以及 参数绑定

2.2 顺序互换

 这个功能使用的比较少,但是我们在这里简单介绍一下。首先我们创建一个进行除法操作的的函数:

double my_divide(double x, double y) { return x / y; }

int main()
{
	std::cout << my_divide(10, 1) << std::endl; // 10
	return 0;
}

现在我想要使用 bind 将被除数和除数的顺序颠倒一下,形成一个新的调用对象,先看一下该适配器的声明:

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

第一个参数是传入可调用对象,之后便是模板参数包,他的使用说明为:

Each argument may either be bound to a value or be a placeholder:
 - If bound to a value, calling the returned function object will always use that value as argument.
 - If a placeholder, calling the returned function object forwards an argument passed to the call (the one whose order number is specified by the placeholder).

想表达的意思主要是:

  • 如果传入的参数为一个固定的值,则可调用对象就会一直使用那个值
  • 如果是一个占位符,则可调用对象使用传入的参数

这里的占位符位于:std::placeholders 来代表一个参数。

 我们来看一下,如果使用 bind 来创建一个和函数功能一模一样的可调用对象:

double my_divide(double x, double y) { return x / y; }

int main()
{
	std::cout << my_divide(10, 1) << std::endl; // 10
	auto func = std::bind(my_divide, std::placeholders::_1, std::placeholders::_2);

	return 0;
}

首先我们传入了函数地址(函数名),之后我们想要在调用时手动输入参数,于是按照顺序传入占位符 1,占位符 2。

 现在我们想要调换一下参数调用顺序,于是我们直接将占位符(一个占位符对应该位置的参数)的顺序调换 :

auto func = std::bind(my_divide, std::placeholders::_2, std::placeholders::_1);

这就完成啦,当我们调用 func(10, 1) 时结果就会变成 0.1

2.3 绑定参数

 这个功能就比较强大了,明明有些函数我们需要传递 3 个参数,但通过绑定,我们可以直接传 2 个,1 个,甚至不传,就比如,还是拿上一个函数举例:

double my_divide(double x, double y) { return x / y; }

int main()
{
	std::cout << my_divide(10, 1) << std::endl; // 普通调用

	auto func1 = std::bind(my_divide, 20, std::placeholders::_1); // 绑定一个参数
	std::cout << func1(10) << std::endl;

	auto func2 = std::bind(my_divide, 20, 10); // 绑定两个参数
	std::cout << func2() << std::endl;

	return 0;
}

还记得我们上面的方案三吗,现在我们再次实现:

class AddClass
{
public:
	int AddFunc(const int& left, const int& right)
	{
		return left + right;
	}
};


int main()
{
	std::function<int(int, int)> func1 = std::bind(&AddClass::AddFunc, AddClass(), std::placeholders::_1, std::placeholders::_2);
	std::cout << func1(1, 2) << std::endl;

	return 0;
}

 大家可能现在觉得可能这个感觉也没啥呀?没感觉有啥用呀?其实是因为我们没有对应的使用场景,没有代入感!还记得在学习多线程时一个函数吗?

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,  
                   void *(*start_routine) (void *), void *arg);

这个函数我们主要关注后两个参数,前者代表我们需要调用的函数,后者是参数。平常使用感觉按部就班就好,但是万一我们是调用一个类中的函数呢?类中的普通成员是带有 this 指针的,和后者的参数不匹配的。之前,我们的解决方案是封装几层调用关系;现在的话,一个 bind 就能解决了!


3. 序言

 在这篇文章中,我们主要介绍了 function 包装器以及 bind 适配器,现在我们对可调用对象又有了一个新的认识,希望大家有所收获!

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值