一步步实现C++11中的std::function和std::bind(上)

目录

一、本文目的

二、std::function和std::bind简介

三、简单实现binder类

四、简单实现std::bind函数

五、简单实现std::function(难点)

六、std::bind如何绑定参数


一、本文目的

        std::function和std::bind是C++11新增特性,可以非常方便实现函数的回调,且非常安全。对于如何使用std::function和std::bind不是本文的重点,本文目的是在于如何自己手动实现std::function和std::bind,只有自己手动实现了才会了解其中的原理,同时实现这两个函数有助于掌握C++模板使用技巧,如模板特例化、偏特例化变参模板等。

二、std::function和std::bind简介

        std::function和std::bind在#include<functional>中,std::function是对可调用函数的封装,保存了函数以及参数,也就是闭包。std::bind是可以将函数和参数进行绑定,打包成binder对象,binder对象可以转化为function对象,如下代码:

// TestBaisc.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "_function.h"

#include <iostream>
#include <functional>

class Print
{
public:
	Print() {}
	virtual ~Print() {}
	int draw(int a, int b, int c)
	{
		std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
		return 0;
	}
};

int print(int a, int b, int c)
{
	std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
	return 0;
}

int main()
{
	std::function<int()> _printf = std::bind(&print, 1, 2, 3);  // 绑定普通函数
	_printf();

	Print pt;
	std::function<int()> _mprintf = std::bind(&Print::draw, &pt, 1, 2, 3);  // 绑定成员函数
	_mprintf();

	std::function<int()> _labprintf = std::bind([=](int a,int b,int c) {  // 绑定lambda表达式
		std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
		return 0;
	}, 1, 2, 3);
	_labprintf();

	system("pause");
	return 0;
}

std::bind可以将将函数和参数打包为function对象f,通过f()延后执行。这种方式可以非常灵活的处理回调函数。上面代码演示事先绑定所以参数,也可以不绑定参数或者部分参数,而是在调用时传入所需要的参数。

int main()
{
	std::function<int(int,int)> _printf = std::bind(&print, std::placeholders::_1, 2, std::placeholders::_2);  // 第1、3个参数需要出入,第2个固定
	_printf(1,3);

	system("pause");
	return 0;
}

三、简单实现binder类

        std::bind绑定函数和参数返回的是_Binder对象,可以通过vs查看(鼠标移到std::bind上),如下:

因此有理由相信_Binder肯定保存了函数和参数包,事实上确实如此,可以查看functional代码。因为functional代码非常混乱,在这里补贴出。

         要想实现std::bind先必须实现_Binder类,该类用于保存函数和参数包。为了简化这里暂时只保存函数,不保存参数,参数保存问题下面再解决。

        为了不冲突,这里取_Binder类名为_binder_,先用它保存一般函数(普通函数、Lamda函数、静态函数)。1)显然_binder_是一个模板类形参为Fx用于传入函数,2)类模板中有一个成员Fx m_f用于保存函数。3)需要时实现一个仿函数用于传入参数包,用m_f进行调用,如下:

template<typename Fx>
class _binder_
{
public:
	_binder_(Fx f) :m_f(f) {}
	template<typename... Args>
	auto operator()(Args... args)   // 仿函数为函数模板,传入参数包
	{
		return (*m_f)(args...);   // 函数调用参数包
	}

private:
	Fx m_f;
};

可以简单使用上面的_binder_类,如下:

int main()
{
	_binder_<int(*)(int, int, int)> f = print;
	f(1, 2, 3);

	system("pause");
	return 0;
}

        上面使用_binder_类实现了保存一般函数的目的,那么类成员函数如何保存呢?_binder_可以保存类成员函数,但是无法实现调用,因为类成员函数调用是需要对象进行调用的,而_binder_中的(*m_f)(args...)并没有和对象挂钩,如下:

int main()
{
	Print pt;
	_binder_<int(Print::*)(int, int, int)> f2 = &Print::draw;
	// f2();   // 错误

	system("pause");
	return 0;
}

所以,我们需要另外实现一个_binder_模板用于处理成员函数的保存。假设为_mbinder_,显然_mbinder_类模板参数除了Fx外,还需要T类类型参数用于保存对象。_mbiner_实现如下:

template<typename Fx, typename T>
class _mbinder_
{
public:
	_mbinder_(Fx f, T t) :m_f(f),m_t(t) {}
	template<typename... Args>
	auto operator()(Args... args)   // 仿函数为函数模板,传入参数包
	{
		return (m_t->*m_f)(args...);   // 函数调用参数包
	}

private:
	Fx m_f;
	T m_t;
};

同样可以使用_mbinder_用于保存类成员函数,如下:

int main()
{
	Print pt;
	_mbinder_<int(Print::*)(int, int, int), Print> f2(&Print::draw, &pt);
	f2(1,2,3);

	system("pause");
	return 0;
}

四、简单实现std::bind函数

        std::bind函数返回的是_Binder对象,由于一般函数和成员函数的binder不一样,那么需要处理,在绑定一般函数时返回的是_binder_,绑定成员函数返回的是_mbinder_,这里我们分别提供返回_binder_和_mbinder_的函数模板,如下:

// 一般函数bind
template<typename Fx>
auto _bind(Fx f)
{
	return _binder_<Fx>(f);
}

// 成员函数bind
template<typename Fx, typename T>
auto _bind(Fx f, T *t)
{
	return _mbinder_<Fx, T>(f, t);
}

 现在我们可以利用_bind实现简单对一般函数和类成员函数的绑定调用,如下:

int main()
{
	auto f = _bind(&print);
	f(1, 2, 3);

	Print pt;
	auto f2 = _bind(&Print::draw, &pt);
	f2(1, 2, 3);

	system("pause");
	return 0;
}

五、简单实现std::function(难点)

        我们已经实现了_Binder,采用auto f = _bind(...)方式可以直接调用f(...),按理说已经差不多了,但是还不够,_bind的返回有点复杂,如上面代码,f的类型为int(*)(int,int,int),f2的类型为int(Print::*)(int,int,int)。如果我们想在另外一个类中实现f2的回调,那么必须使用int(Print::*)(int,int,int)保存成员函数指针,那么这个类就依赖了Print类;如果我们需要回调很多类形如int(class::*)(int,int,int)的函数,那么就需要分别添加各个类的int(class::*)(int,int,int)函数指针,然后在回函数中添加分别调用。所以很有必要将int(*)(int,int,int)、int(class::*)(int,int,int)转化为同一种形式,这种形式就是std::function。

        std::function形式为std::function<R(Args...)>,R为返回值,Args...为参数包,所以std::function模板形式为template<typename Fx>,Fx就是R(Args...)函数形式。std::bind返回值为_Binder,但是可以将_Binder对象赋给std::function对象,显然std::function构造函数中实现了对_Binder的转化。我们实现一个简单的_function如下:

template<typename Fx>
class _function
{
public:
	template<typename _binderType>
	_function(_binderType binder)
	{

	}
	template<typename... Args>
	auto operator()(Args... args)
	{
		// return binder(args...);   // 希望如此
	}

private:
	
};

        仿函数里面,我们希望调用binder(args...),由于构造函数和仿函数是异步的,所以必须想办法在构造函数中保存binder。最先想到的办法是在_function中添加_binderType m_binder,但是_binderType并没有作为模板形参,所以无法这样做,那么是否可以将_binderType作为模板形参呢?如果将_binderType作为形参即template<typename Fx,typename _binderType> class _function,那么_function的形式就不能统一。

        那么如何保存binder呢?这里有一个技巧,虽然_function不能通过成员函数保存binder,但是可以通过另外一个类去保存binder呢?定义类binder_wrapper_impl,该类为接口类,接口类中不含有binder,binder的保存放在binder_wrapper中实现,binder_wrapper继承binder_wrapper_impl,那么_function中可以直接包含binder_wrapper_impl指针,创建时使用binder_wrapper进行创建,并传入binder。 在仿函数中通过虚接口call进行调用,在binder_wrapper中实现虚接口call,使用保存下来的binder进行调用。实现大体如下:

// 错误代码
class binder_wrapper_impl
{
public:
	template<typename... Args>
	virtual auto call(Args... args)
	{
		return 1;
	}
};

template<typename _binderType>
class binder_wrapper:public binder_wrapper_impl
{
public:
	binder_wrapper(_binderType binder) :m_binder(binder) {}
	template<typename... Args>
	virtual auto call(Args... args)
	{
		return m_binder(args...);
	}

private:
	_binderType m_binder;
};


template<typename Fx>
class _function
{
public:
	template<typename _binderType>
	_function(_binderType binder)
	{
		m_binder = new binder_wrapper<_binderType>(binder);
	}
	virtual ~_function() {
		delete m_binder;
	}
	template<typename... Args>
	auto operator()(Args... args)
	{
		return m_binder->call(args...);
	}

private:
	binder_wrapper_impl *m_binder = nullptr;
};

        上面代码不能编译通过,原因时类函数模板不能为虚函数,故必须将模板形参提到类中,即binder_wrapper_impl为类模板,所以binder_wrapper_impl和binder_wrapper修改为如下:

template<typename... Args>
class binder_wrapper_impl
{
public:
	virtual auto call(Args... args) { return 0; }
};

template<typename _binderType, typename... Args>
class binder_wrapper:public binder_wrapper_impl<Args...>
{
public:
	binder_wrapper(_binderType binder) :m_binder(binder) {}
	virtual auto call(Args... args)
	{
		return m_binder(args...);
	}

private:
	_binderType m_binder;
};

        上面代码还是无法编译通过,原因是虚函数不能含有auto返回类型,因此binder_wrapper_impl模板形参还必须包含返回类型,所以上面代码需要改为如下形式:

template<typename R, typename... Args>
class binder_wrapper_impl
{
public:
	virtual R call(Args... args) { return R(); }
};

template<typename _binderType, typename R, typename... Args>
class binder_wrapper:public binder_wrapper_impl<R, Args...>
{
public:
	binder_wrapper(_binderType binder) :m_binder(binder) {}
	virtual R call(Args... args)
	{
		return m_binder(args...);
	}

private:
	_binderType m_binder;
};

这就造成了_functiom_binder的形式定义必须为:binder_wrapper_impl<R, Args...> m_binder;所以_function模板形参必须有返回值类和参数包类型,所以_function修改后如下:

// 泛化版本
template<typename R, typename... Args>
class _function;

// 偏特例化
template<typename R, typename... Args>
class _function<R(Args...)>
{
public:
	template<typename _binderType>
	_function(_binderType binder)
	{
		m_binder = new binder_wrapper<_binderType, R, Args...>(binder);
	}
	virtual ~_function() {
		delete m_binder;
	}
	auto operator()(Args... args)
	{
		return m_binder->call(args...);
	}

private:
	binder_wrapper_impl<R, Args...> *m_binder = nullptr;
};

 这里用到了类模板的偏特例化。

完整代码如下:

template<typename Fx>
class _binder_
{
public:
	_binder_(Fx f) :m_f(f) {}
	template<typename... Args>
	auto operator()(Args... args)   // 仿函数为函数模板,传入参数包
	{
		return (*m_f)(args...);   // 函数调用参数包
	}

private:
	Fx m_f;
};

template<typename Fx, typename T>
class _mbinder_
{
public:
	_mbinder_(Fx f, T *t) :m_f(f),m_t(t) {}
	template<typename... Args>
	auto operator()(Args... args)   // 仿函数为函数模板,传入参数包
	{
		return (m_t->*m_f)(args...);   // 函数调用参数包
	}

private:
	Fx m_f;
	T *m_t;
};

//
// 一般函数bind
template<typename Fx>
auto _bind(Fx f)
{
	return _binder_<Fx>(f);
}

// 成员函数bind
template<typename Fx, typename T>
auto _bind(Fx f, T *t)
{
	return _mbinder_<Fx, T>(f, t);
}

//
template<typename R, typename... Args>
class binder_wrapper_impl
{
public:
	virtual R call(Args... args) { return R(); }
};

template<typename _binderType, typename R, typename... Args>
class binder_wrapper:public binder_wrapper_impl<R, Args...>
{
public:
	binder_wrapper(_binderType binder) :m_binder(binder) {}
	virtual R call(Args... args)
	{
		return m_binder(args...);
	}

private:
	_binderType m_binder;
};


// 泛化版本
template<typename R, typename... Args>
class _function;

// 偏特例化
template<typename R, typename... Args>
class _function<R(Args...)>
{
public:
	template<typename _binderType>
	_function(_binderType binder)
	{
		m_binder = new binder_wrapper<_binderType, R, Args...>(binder);
	}
	virtual ~_function() {
		delete m_binder;
	}
	auto operator()(Args... args)
	{
		return m_binder->call(args...);
	}

private:
	binder_wrapper_impl<R, Args...> *m_binder = nullptr;
};

可以实现调用,如下:

int main()
{
	_function<int(int,int,int)> f = _bind(&print);
	f(1, 2, 3);

	Print pt;
	_function<int(int,int,int)> f2 = _bind(&Print::draw, &pt);
	f2(1, 2, 3);

	_function<int(int, int, int)> f3 = _bind(&[=](int a, int b, int c) {
		std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
		return 0;
	});
	f3(1, 2, 3);

	system("pause");
	return 0;
}

总结:在讲述如何实现std::function时,我并未直接写出最终实现代码,而是一步步引导如何实现,否则读者可能会觉得如此实现谁能想的到?实现上只要写多接触多了,其中的技巧就会熟练。可如何保存binder,实现延期回调是整个最难地方,用到知识点和技巧总结如下:

  1.  在_function构造函数中实现_Binder的转化;
  2. 通过间接类实现binder的保存,通过继承机制,避免_function模板形参中包含_binderType;
  3. 类模板的偏特例化使用。

接下来需要实现_Binder参数包的保存,由于参数包的保存技巧性非常大、也非常难,将在下一篇解决该问题。 

内容概要:本文档详细介绍了在三台CentOS 7服务器(IP地址分别为192.168.0.157、192.168.0.158和192.168.0.159)上安装和配置Hadoop、Flink及其他大数据组件(如Hive、MySQL、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala)的具体步骤。首先,文档说明了环境准备,包括配置主机名映射、SSH免密登录、JDK安装等。接着,详细描述了Hadoop集群的安装配置,包括SSH免密登录、JDK配置、Hadoop环境变量设置、HDFS和YARN配置文件修改、集群启动与测试。随后,依次介绍了MySQL、Hive、Sqoop、Kafka、Zookeeper、HBase、Spark、Scala和Flink的安装配置过程,包括解压、环境变量配置、配置文件修改、服务启动等关键步骤。最后,文档提供了每个组件的基本测试方法,确保安装成功。 适合人群:具备一定Linux基础和大数据组件基础知识的运维人员、大数据开发工程师以及系统管理员。 使用场景及目标:①为大数据平台建提供详细的安装指南,确保各组件能够顺利安装和配置;②帮助技术人员快速掌握Hadoop、Flink等大数据组件的安装与配置,提升工作效率;③适用于企业级大数据平台的建与维护,确保集群稳定运行。 其他说明:本文档不仅提供了详细的安装步骤,还涵盖了常见的配置项解释和故障排查建议。建议读者在安装过程中仔细阅读每一步骤,并根据实际情况调整配置参数。此外,文档中的命令和配置文件路径均为示例,实际操作时需根据具体环境进行适当修改。
在无线通信领域,天线阵列设计对于信号传播方向和覆盖范围的优化至关重要。本题要求设计一个广播电台的天线布局,形成特定的水平面波瓣图,即在东北方向实现最大辐射强度,在正东到正北的90°范围内辐射衰减最小且无零点;而在其余270°范围内允许出现零点,且正西和西南方向必须为零。为此,设计了一个由4个铅垂铁塔组成的阵列,各铁塔上的电流幅度相等,相位关系可自由调整,几何布置和间距不受限制。设计过程如下: 第一步:构建初级波瓣图 选取南北方向上的两个点源,间距为0.2λ(λ为电磁波波长),形成一个端射阵。通过调整相位差,使正南方向的辐射为零,计算得到初始相位差δ=252°。为了满足西南方向零辐射的要求,整体相位再偏移45°,得到初级波瓣图的表达式为E1=cos(36°cos(φ+45°)+126°)。 第二步:构建次级波瓣图 再选取一个点源位于正北方向,另一个点源位于西南方向,间距为0.4λ。调整相位差使西南方向的辐射为零,计算得到相位差δ=280°。同样整体偏移45°,得到次级波瓣图的表达式为E2=cos(72°cos(φ+45°)+140°)。 最终组合: 将初级波瓣图E1和次级波瓣图E2相乘,得到总阵的波瓣图E=E1×E2=cos(36°cos(φ+45°)+126°)×cos(72°cos(φ+45°)+140°)。通过编程实现计算并绘制波瓣图,可以看到三个阶段的波瓣图分别对应初级波瓣、次级波瓣和总波瓣,最终得到满足广播电台需求的总波瓣图。实验代码使用MATLAB编写,利用polar函数在极坐标下绘制波瓣图,并通过subplot分块显示不同阶段的波瓣图。这种设计方法体现了天线阵列设计的基本原理,即通过调整天线间的相对位置和相位关系,控制电磁波的辐射方向和强度,以满足特定的覆盖需求。这种设计在雷达、卫星通信和移动通信基站等无线通信系统中得到了广泛应用。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值