揭开bind模板薄薄的面纱

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

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

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

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

考虑到实现的先后顺序,我先讲第2步。

实现调用参数对绑定参数的替换:

何为绑定参数?何为调用参数?

请看下面的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string>

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<int i>
    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 A1 = PlaceHolder<1>, 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<typename T>
        T get(T t)
        {
            return t;
        }
    };
}

相较于以前的实现,这里将绑定参数和调用参数统一化了,因为它们的本意就是存储一组参数列表,然后进行取值以实现数据交换。

如何实现二者的替换?

演示代码:

my::ArgumentsList<my::PlaceHolder<3>, int, my::PlaceHolder<1>> BL(my::_3, 2, my::_1);
my::ArgumentsList<int, float, std::string> 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<typename R, typename A1, typename BL>
    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<typename V1>
        R operator()(V1 v1)
        {
            return m_f( ArgumentsList<V1>(v1).get(m_bl.m_a1) );
        }
    };

    template<typename R, typename A1, typename T1>
    BindFunction1<R, A1, ArgumentsList<T1> > bind(R(*f)(A1), T1 t1)
    {
        return BindFunction1<R, A1, ArgumentsList<T1> >(f, ArgumentsList<T1>(t1));
    }
}

主体是BindFunction1类模板,bind函数模板用于调用时推导。

下面的调用代码使用刚刚自定义的实现替换了开头的标准bind,如下:

#include <stdio.h>
#include <stdlib.h>
#include <string>

void say(const std::string& str)
{
    printf("%s \n", str.c_str());
}

void main()
{
    my::bind(say, my::_1)("hello");
}
扩展到类成员函数:
namespace my
{
    template<typename R, typename C, typename A1, typename BL>
    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<typename V1>
        R operator()(V1 v1)
        {
            return (m_p->*m_f)( ArgumentsList<V1>(v1).get(m_bl.m_a1) );
        }
    };

    template<typename R, typename C, typename A1, typename T1>
    BindClass1<R, C, A1, ArgumentsList<T1> > bind(R(C::*f)(A1), C* p, T1 t1)
    {
        return BindClass1<R, C, A1, ArgumentsList<T1> >(f, p, ArgumentsList<T1>(t1));
    }
}

用法:

#include <stdio.h>
#include <stdlib.h>
#include <string>

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<typename R, typename A1, typename A2, typename BL>
    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<typename V1>
        R operator()(V1 v1)
        {
            ArgumentsList<V1> cl(v1);
            return m_f(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
        }

        template<typename V1, typename V2>
        R operator()(V1 v1, V2 v2)
        {
            ArgumentsList<V1, V2> cl(v1, v2);
            return m_f(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
        }
    };

    template<typename R, typename C, typename A1, typename A2, typename BL>
    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<typename V1>
        R operator()(V1 v1)
        {
            ArgumentsList<V1> cl(v1);
            return (m_p->*m_f)(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
        }

        template<typename V1, typename V2>
        R operator()(V1 v1, V2 v2)
        {
            ArgumentsList<V1, V2> cl(v1, v2);
            return (m_p->*m_f)(cl.get(m_bl.m_a1), cl.get(m_bl.m_a2));
        }
    };

    template<typename R, typename A1, typename A2, typename T1, typename T2>
    BindFunction2<R, A1, A2, ArgumentsList<T1, T2> > bind(R(*f)(A1, A2), T1 t1, T2 t2)
    {
        return BindFunction2<R, A1, A2, ArgumentsList<T1, T2> >(f, ArgumentsList<T1, T2>(t1, t2));
    }
    template<typename R, typename C, typename A1, typename A2, typename T1, typename T2>
    BindClass2<R, C, A1, A2, ArgumentsList<T1, T2> > bind(R(C::*f)(A1, A2), C* p, T1 t1, T2 t2)
    {
        return BindClass2<R, C, A1, A2, ArgumentsList<T1, T2> >(f, p, ArgumentsList<T1, T2>(t1, t2));
    }
}

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容