揭开bind模板薄薄的面纱

以前我也写过类似的文章,大家可以翻看我的旧文,之所以旧题重谈,这是因为最近在项目中又有涉及回调的问题,关于回调用法的总结,大家可以参看我的另一篇文章《c/c++回调技术总结》,地址在:https://www.jianshu.com/p/74668df25548 。

我不建议在处理回调应用时使用抽象基类,关于具体原因我在《c/c++回调技术总结》文中有说明,所以如果不想自己造轮子,bind和function是c++回调非常好的选择。本文旨在为bind的实现原理感兴趣的读者揭开那么一层薄薄的面纱,如果您是一位极致的应用主义者,我想这篇文章对您的帮助应该不大。

bind模板的实现过程可以总结为两步:

  1. 对回调函数进行包装,回调函数包括对自由函数(类静态函数)、类成员函数等类型。
  2. 实现调用参数对绑定参数的替换。

考虑到实现的先后顺序,我先讲第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));
    }
}

扩展到更多的参数以此类推,不做陈述,请朋友们自行推理。

你可能感兴趣的:(揭开bind模板薄薄的面纱)