以前我也写过类似的文章,大家可以翻看我的旧文,之所以旧题重谈,这是因为最近在项目中又有涉及回调的问题,关于回调用法的总结,大家可以参看我的另一篇文章《c/c++回调技术总结》,地址在:https://www.jianshu.com/p/74668df25548 。
我不建议在处理回调应用时使用抽象基类,关于具体原因我在《c/c++回调技术总结》文中有说明,所以如果不想自己造轮子,bind和function是c++回调非常好的选择。本文旨在为bind的实现原理感兴趣的读者揭开那么一层薄薄的面纱,如果您是一位极致的应用主义者,我想这篇文章对您的帮助应该不大。
bind模板的实现过程可以总结为两步:
- 对回调函数进行包装,回调函数包括对自由函数(类静态函数)、类成员函数等类型。
- 实现调用参数对绑定参数的替换。
考虑到实现的先后顺序,我先讲第2步。
实现调用参数对绑定参数的替换:
何为绑定参数?何为调用参数?
请看下面的代码:
#include
#include
#include
void say(const std::string& str)
{
printf("%s \n", str.c_str());
}
void main()
{
std::bind(say, std::placeholders::_1)("hello");
}
在 std::bind(say, std::placeholders::_1)("hello"); 这一行代码中:
std::placeholders::_1 就是绑定参数。
"hello" 就是调用参数。
定义:
绑定参数就是函数真正执行前,提前设置的形式参数,如占位符,当然也可以是具体的数据类型值。
调用参数就是函数真正执行时,传递的具体的数据类型值。
如何实现参数的存储?
涉及到模板编程,请看下面的代码:
namespace my
{
// 占位符
template
struct PlaceHolder {};
static PlaceHolder<1> _1;
static PlaceHolder<2> _2;
static PlaceHolder<3> _3;
static PlaceHolder<4> _4;
static PlaceHolder<5> _5;
static PlaceHolder<6> _6;
static PlaceHolder<7> _7;
static PlaceHolder<8> _8;
static PlaceHolder<9> _9;
// 参数列表模板
template, typename A2 = PlaceHolder<2>, typename A3 = PlaceHolder<3>,
typename A4 = PlaceHolder<4>, typename A5 = PlaceHolder<5>, typename A6 = PlaceHolder<6>,
typename A7 = PlaceHolder<7>, typename A8 = PlaceHolder<8>, typename A9 = PlaceHolder<9>>
struct ArgumentsList
{
A1 m_a1;
A2 m_a2;
A3 m_a3;
A4 m_a4;
A5 m_a5;
A6 m_a6;
A7 m_a7;
A8 m_a8;
A9 m_a9;
// 构造时进行参数关联
ArgumentsList(A1 a1 = _1, A2 a2 = _2, A3 a3 = _3, A4 a4 = _4, A5 a5 = _5, A6 a6 = _6, A7 a7 = _7, A8 a8 = _8, A9 a9 = _9)
: m_a1(a1), m_a2(a2), m_a3(a3), m_a4(a4), m_a5(a5), m_a6(a6), m_a7(a7), m_a8(a8), m_a9(a9) {}
// 获取指定占位符的参数
A1 get(PlaceHolder<1>)
{
return m_a1;
}
A2 get(PlaceHolder<2>)
{
return m_a2;
}
A3 get(PlaceHolder<3>)
{
return m_a3;
}
A4 get(PlaceHolder<4>)
{
return m_a4;
}
A5 get(PlaceHolder<5>)
{
return m_a5;
}
A6 get(PlaceHolder<6>)
{
return m_a6;
}
A7 get(PlaceHolder<7>)
{
return m_a7;
}
A8 get(PlaceHolder<8>)
{
return m_a8;
}
A9 get(PlaceHolder<9>)
{
return m_a9;
}
// 获取非占位符参数
template
T get(T t)
{
return t;
}
};
}
相较于以前的实现,这里将绑定参数和调用参数统一化了,因为它们的本意就是存储一组参数列表,然后进行取值以实现数据交换。
如何实现二者的替换?
演示代码:
my::ArgumentsList, int, my::PlaceHolder<1>> BL(my::_3, 2, my::_1);
my::ArgumentsList CL(1, 2.0, "3");
std::string v1 = CL.get(BL.m_a1);
int v2 = CL.get(BL.m_a2);
int v3 = CL.get(BL.m_a3);
上面BL绑定了3个参数,第1、3参数为占位符,第2参数为整数2。
CL顺次绑定了int、float、std::string三个参数。
当调用CL.get()方法时,传递给它的参数类型将引起CL实际取出哪个绑定参数。
上面v1、v2、v3的结果为:
v1 => "3"
v2 => 2 // 2.0没有被交换
v3 => 1
对回调函数进行包装:
基本的1个参数的自由函数包装:
模板包装如下:
namespace my
{
template
class BindFunction1
{
private:
typedef R(*F)(A1);
F m_f;
BL m_bl;
public:
BindFunction1(F f, BL bl) : m_f(f), m_bl(bl) {}
R operator()()
{
return m_f( ArgumentsList<>().get(m_bl.m_a1) );
}
template
R operator()(V1 v1)
{
return m_f( ArgumentsList(v1).get(m_bl.m_a1) );
}
};
template
BindFunction1 > bind(R(*f)(A1), T1 t1)
{
return BindFunction1 >(f, ArgumentsList(t1));
}
}
主体是BindFunction1类模板,bind函数模板用于调用时推导。
下面的调用代码使用刚刚自定义的实现替换了开头的标准bind,如下:
#include
#include
#include
void say(const std::string& str)
{
printf("%s \n", str.c_str());
}
void main()
{
my::bind(say, my::_1)("hello");
}
扩展到类成员函数:
namespace my
{
template
class BindClass1
{
private:
typedef R(C::*F)(A1);
F m_f;
C* m_p;
BL m_bl;
public:
BindClass1(F f, C* p, BL bl) : m_f(f), m_p(p), m_bl(bl) {}
R operator()()
{
return (m_p->*m_f)( ArgumentsList<>().get(m_bl.m_a1) );
}
template
R operator()(V1 v1)
{
return (m_p->*m_f)( ArgumentsList(v1).get(m_bl.m_a1) );
}
};
template
BindClass1 > bind(R(C::*f)(A1), C* p, T1 t1)
{
return BindClass1 >(f, p, ArgumentsList(t1));
}
}
用法:
#include
#include
#include
class CPeople
{
public:
void say(const std::string& str)
{
printf("%s \n", str.c_str());
}
};
void main()
{
CPeople people;
my::bind(&CPeople::say, &people, my::_1)("hello");
}
扩展到多个参数:
扩展到2个参数的代码如下:
namespace my
{
template
class BindFunction2
{
private:
typedef R(*F)(A1, A2);
F m_f;
BL m_bl;
public:
BindFunction2(F f, BL bl) : m_f(f), m_bl(bl) {}
R operator()()
{
ArgumentsList<> cl;
return m_f(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
}
template
R operator()(V1 v1)
{
ArgumentsList cl(v1);
return m_f(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
}
template
R operator()(V1 v1, V2 v2)
{
ArgumentsList cl(v1, v2);
return m_f(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
}
};
template
class BindClass2
{
private:
typedef R(C::*F)(A1, A2);
F m_f;
C* m_p;
BL m_bl;
public:
BindClass2(F f, C* p, BL bl) : m_f(f), m_p(p), m_bl(bl) {}
R operator()()
{
ArgumentsList<> cl;
return (m_p->*m_f)(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
}
template
R operator()(V1 v1)
{
ArgumentsList cl(v1);
return (m_p->*m_f)(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
}
template
R operator()(V1 v1, V2 v2)
{
ArgumentsList cl(v1, v2);
return (m_p->*m_f)(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
}
};
template
BindFunction2 > bind(R(*f)(A1, A2), T1 t1, T2 t2)
{
return BindFunction2 >(f, ArgumentsList(t1, t2));
}
template
BindClass2 > bind(R(C::*f)(A1, A2), C* p, T1 t1, T2 t2)
{
return BindClass2 >(f, p, ArgumentsList(t1, t2));
}
}
扩展到更多的参数以此类推,不做陈述,请朋友们自行推理。