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

本文详细介绍了C++11中如何保存和展开参数包,包括std::bind的参数绑定机制,参数包的保存方法,以及如何使用tuple和模板元编程技术实现参数包的展开。通过构造索引序列_index_seq_,实现了参数包的自动展开,最终展示了如何在_binder_和_mbinder_类中保存和调用参数包。
摘要由CSDN通过智能技术生成

目录

一、本文目的

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

三、参数包如何保存

四、tuple展开

五、构造索引系列_index_seq_

六、通过_index_seq_展开tuple 

七、binder保存参数


一、本文目的

        上一篇介绍了std::function和std::bind实现方式,可以保存一般函数和成员函数,但并未实现对参数包的保存。由于参数包的保存也是一个难点,所以将该内容作为一个单独的篇幅进行介绍。通过该篇可以了解参数包的实现原理,同时也能掌握C++模板的实现技巧。

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

        首先我们先了解下,std::bind的用法,看它是如何工作的?        

        std::bind可以不绑定任何参数,也可以只绑定部分参数。不绑定参数得到的std::function调用时,需要将所有参数传入,std::function模板实参为完整函数形式。 如上面例子std::function<int(int,int,int)>。

        如果是绑定部分参数,也就是部分参数指定,其他参数待定(调用时传入),需要在绑定时指定固定参数,其他参数使用占位符代替。占位符个数就是std::function模板形参个数,类型必须一致。总结如下:

  1. 固定参数位置对应函数形参位置;
  2. 占位符必须从_1开始排列,占位符的序号就是函数参数位置顺序序号(不包括固定参数);
  3. 可以指定占位符顺序,占位符顺序决定了std::function模板形参顺序;可以认为bind自定义封装了另外一个函数;
  4. 调用时顺序和占位符保持一致。
class Print
{
public:
	Print() {}
	virtual ~Print() {}
	void draw(int a, double b, float c)
	{
		std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
	}
};

int main()
{
	Print pt;

	// std::placeholders::_1->a,1->b,std::placeholders::_2->c; 占位符顺和函数参数顺序保持一致
	// std::function形参顺序和bind中占位符顺序保持一致
	std::function<void(float, int)> f2 = std::bind(&Print::draw, &pt, std::placeholders::_2, 1, std::placeholders::_1);
	f2(2.2, 3.5); // 输出a = 3,b = 1,c = 2.2

	system("pause");
	return 0;
}

三、参数包如何保存

       要保存参数包,想当然是如下写法:

// 错误代码
template<typename Fx, typename... Args>
class _binder_
{
public:
	_binder_(Fx f, Args... args) :m_f(f), m_args(args...) {}
	auto operator()()
	{
		return (*m_f)(args...);
	}

private:
	Fx m_f;
	Args... m_args;   // 错误写法
};

但是Args... m_args没有这种写法,无法编译通过。所以需要考虑另外一种保存参数包的方法。C++11中的tuple可以保存不同类型的参数,可以使用它。如下:

template<typename Fx, typename... Args>
class _binder_
{
public:
	_binder_(Fx f, Args... args) :m_f(f), m_tu(args...) {}
	auto operator()()
	{
		// (*m_f)(?)
		return 0;
	}

private:
	Fx m_f;
	tuple<Args...> m_tu;
};

         那么如何使用m_f调用m_tu呢?调用形式必须为(*m_f)(args...),实参必须是包展开形式,不能直接为m_tu,(*m_f)(m_tu)这种写法是错误的。所以必须对m_tu进行展开调用。这里先讲一下如何对tuple进行展开。

四、tuple展开

        考虑如下代码:

class Print
{
public:
	template<typename... Args>
	void print(Args... args)
	{
		tuple<Args...> tu(args...);
		for (int i = 0; i < sizeof(tu); i++)
			cout << std::get<i>(tu);  // 错误
	}
};

int main()
{
	Print pt;
	pt.print(1, "cjf", 'c');

	system("pause");
	return 0;
}

该代码想传入任意类型参数将其打印出来。在Print::print中将参数包转化为tuple,企图通过遍历tuple实现打印各个参数。上述代码是错误的,原因在于std::get使用,get中的<>数值必须是编译常量,是不能接受变量的。可以改为下面这种方式:

template<typename... Args>
class Print
{
public:
	Print(Args... args) :m_tu(args...) {}

	template<size_t... I>
	void print()
	{
		int arry[] = { (cout << std::get<I>(m_tu),0)... };
	}

private:
	tuple<Args...> m_tu;
};

template<typename... Args>
Print<Args...> make_print(Args... args)
{
	return Print<Args...>(args...);
}

int main()
{
	auto _print = make_print(1, "cjf", 'c');
	_print.print<0, 1, 2>();

	system("pause");
	return 0;
}

        上面构造了模板函数template<0,1,2> void print(),调用(cout << std::get<I>(m_tu),0)...,会展开成(cout<<std::get<0>(m_tu),0),(cout<<std::get<1>(m_tu),0),(cout<<std::get<2>(m_tu),0)形式,括号0会作为数组元素值。

        通过make_print模板传入Args...构造模板实例,避免手动写,太过复杂。上面问题在于,我们要手动指定tuple的所有索引,如果数组个数是100个,根本无法写。最好的调用形式为_print.print()。

        事实上,我们在构造Print时,传入Args...参数包,是知道参数个数的,问题是如何在编译时确定template<0,1,2> void print()模板,也就是必须产生template<0,1,2...>这样的索引系列。

五、构造索引系列_index_seq_

        现在关键问题是如何产生索引序列(模板)。这里用到了类模板递归继承技巧,直接贴出代码:

template<size_t... I>
struct _index_seq_ {};

template<size_t N, size_t... I>
class _make_index_seq_ :public _make_index_seq_<N - 1, N - 1, I...> {};

template<size_t... I>
class _make_index_seq_<0, I...> :public _index_seq_<I...> {};

        _index_seq_为索引序列。_make_index_seq_用来产生索引序列,当我们传入_make_index_seq_<3>()时,会产生如下编译结果:

  1. 先产生template<3> class _make_index_seq_;
  2. 而template<5> class _make_index_seq_继承t_make_index_seq_<3-1,3-1>继而会产生模板实例template<2,2> _make_index_seq_;
  3. 同理template<2,2> _make_index_seq_会继承_make_index_seq_<1,1,2>;
  4. _make_index_seq_<1,1,2>继承_make_index_seq_<0,0,1,2>;
  5. _make_index_seq_<0,0,1,2>继承_index_seq_<0,1,2>结束。
  6. 也就时_make_index_seq_<3>继承了_index_seq_<0,1,2>。

六、通过_index_seq_展开tuple 

        根据上面方法,代码改为如下:

template<size_t... I>
struct _index_seq_ {};

template<size_t N, size_t... I>
class _make_index_seq_ :public _make_index_seq_<N - 1, N - 1, I...> {};

template<size_t... I>
class _make_index_seq_<0, I...> :public _index_seq_<I...> {};

template<typename... Args>
class Print
{
public:
	Print(Args... args) :m_tu(args...) {}
	void operator()()
	{
		print(m_seq);
	}
	template<size_t... I>
	void print(_index_seq_<I...>)
	{
		int arry[] = { (cout << std::get<I>(m_tu),0)... };
	}

private:
	_make_index_seq_<sizeof...(Args)> m_seq;
	tuple<Args...> m_tu;
};

template<typename... Args>
Print<Args...> make_print(Args... args)
{
	return Print<Args...>(args...);
}

int main()
{
	auto _print = make_print(1, "cjf", 'c');
	_print();

	system("pause");
	return 0;
}

         在Print构造函数中添加了_make_index_seq_<sizeof...(Args)> m_seq,编译时会自动产生_index_seq_。在Print中添加仿函数,调用print(m_seq),将其m_seq传给_index_seq_,因为_make_index_seq_<N>继承了_index_seq_<0,1,2,...,N-1>,所以可以直接将m_seq赋给_index_seq_。

        事实上,C++14已经为我们准备了interger_sequence、index_sequence、make_interger_sequence、make_index_sequence,大家有兴趣可以参考:https://en.cppreference.com/w/cpp/utility/integer_sequence

七、binder保存参数

        按照上面方式,我们现在可以将第一篇中的_binder_和_mbinder_来保存参数包(所以参数),部分参数保存将在下一篇幅介绍。代码如下:

#pragma once
#include <tuple>

template<size_t... I>
struct _index_seq_ {};

template<size_t N, size_t... I>
class _make_index_seq_: public _make_index_seq_<N-1, N-1, I...> {};

template<size_t... I>
class _make_index_seq_<0, I...> : public _index_seq_<I...> {};

//
template<typename Fx, typename... Args>
class _binder_
{
public:
	_binder_(Fx f, Args... args) :m_f(f),m_tu(args...) {}
	auto operator()()
	{
		return call(m_seq);
	}
	template<size_t... I>
	auto call(_index_seq_<I...>)
	{
		return (*m_f)(std::get<I>(m_tu)...);
	}

private:
	std::tuple<Args...> m_tu;
	_make_index_seq_<sizeof...(Args)> m_seq;
	Fx m_f;
};

template<typename Fx, typename T, typename... Args>
class _mbinder_
{
public:
	_mbinder_(Fx f, T *t, Args... args) :m_f(f),m_t(t),m_tu(args...) {}
	auto operator()()
	{
		return call(m_seq);
	}
	template<size_t... I>
	auto call(_index_seq_<I...>)
	{
		return (m_t->*m_f)(std::get<I>(m_tu)...);
	}

private:
	std::tuple<Args...> m_tu;
	_make_index_seq_<sizeof...(Args)> m_seq;
	Fx m_f;
	T *m_t;
};

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

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

//
template<typename R, typename... Args>
class binder_wrapper_impl
{
public:
	virtual R call() { 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()
	{
		return m_binder();
	}

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()()
	{
		return m_binder->call();
	}

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

调用如下:

class Print
{
public:
	int draw(int a, int b, int c)
	{
		cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
		return 0;
	}
};

int print(int a, int b, int c)
{
	cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
	return 0;
}

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

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

	system("pause");
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值