游戏开发论坛

 找回密码
 立即注册
搜索
查看: 2817|回复: 0

《解析boost》(二)bind

[复制链接]

15

主题

83

帖子

123

积分

注册会员

Rank: 2

积分
123
发表于 2004-4-12 07:51:00 | 显示全部楼层 |阅读模式
(二)bind
我们在写程序的时候经常需要把一些调用延迟到以后,这也就是所谓的“command”模式,一个典型的例子就是绘图时候的脏矩形管理,我们在调用画图函数的时候并不直接画图,而是创建一个command,然后把这些command保存到一个队列中,然后在最终需要画图的时候再计算出脏矩形,然后再画图,向下面这样:

struct DrawCommand
{
        Image* m_iamge;
        RECT m_rect;
        int m_flag;

        DrawCommand(Image* iamge, RECT& rect, int flag)
        : m_iamge(image), m_rect(rect), m_flag(flag)  { assert(image); }

        void Draw(RECT& rect) { assert(m_iamge); m_iamge->Draw_Impl(rect, m_flag); }
}

class DirtyRect_Drawer
{
private:
        std::vector<DrawCommand*> m_draw_queue;
        RECT m_dirtyrect;
private:
        void CalcDirtyRect()      { ... //use m_draw_queue to calc m_dirtyrect }
        static void Draw(DrawCommand* p)
        {
                assert(p);
                p->Draw(m_dirtyrect);
                delete p;
        }
public:
        void push(DrawCommand* p) { assert(p); m_draw_queue->push_back(p); }
        void FlushDraw()
        {
                CalcDirtyRect();
                std::for_each(m_draw_queue.begin(), m_draw_queue.end(), Draw);
                m_draw_queue.clear();
        }
};

上面的DrawCommand应该使用自己的内存分配方案,也就是要重载new,不然你会受到效率的惩罚,因为C++的默认new实在是慢得要死。并且DirtyRect_Drawer应该是个单件。现在我们就将就向下面这样使用:

namespace
{
        DirtyRect_Drawer _Drawer;
};

extern Image* image1;
extern Image* image2;

int main()
{       
        RECT rect1(0, 0, 100, 100), rect2(50, 50, 100, 100);
        _Drawer.push(new DrawCommand(image1, rect1, SIMPLE_DRAW));
        _Drawer.push(new DrawCommand(image1, rect2, SIMPLE_DRAW));
        _Drawer.FlushDraw();
}

就是这样,但是当这样的Command越来越多的时候,代码重复就越来越大,实现也是枯燥无味。这个时候我们需要一个可以泛用的方案。这就是function和bind。

我们先来看看他的效果,然后再来看他的实现原理。

typedef boost::function<void(RECT&)> DrawCommand;

class DirtyRect_Drawer
{
private:
        std::vector<DrawCommand*> m_draw_queue;
        RECT m_dirtyrect;
private:
        void CalcDirtyRect()      { ... //use m_draw_queue }
        static void Draw(DrawCommand* p)
        {
                assert(p);
                (*p)(m_dirtyrect);
                delete p;
        }
public:
        void push(DrawCommand* p) { assert(p); m_draw_queue->push_back(p); }
        void FlushDraw()
        {
                CalcDirtyRect();
                std::for_each(m_draw_queue.begin(), m_draw_queue.end(), Draw);
                m_draw_queue.clear();
        }
};

namespace
{
        DirtyRect_Drawer _Drawer;
};

extern Image* image1;
extern Image* image2;

int main()
{
        RECT rect1(0, 0, 100, 100), rect2(50, 50, 100, 100);
        _Drawer.push( new DrawCommand(&Image:raw, image1, rect1, SIMPLE_DRAW) );
        _Drawer.push( new DrawCommand(&Image::Draw, image1, rect2, SIMPLE_DRAW) );
        _Drawer.FlushDraw();
}

是不是很方便,DrawCommand根本就不用写,他本质上就是:boost::_bi::bind_t< void, Image::*, list3<image*, RECT&, int> >,我们用boost::function<void(RECT&)>来保存他,而Command实际上就是:operator (RECT&)。

不仅如此,bind还提供了只指定一部分参数的方法:

void kao(int i, float j) { }

int i = 10;

boost::bind(&kao, _1, 10.0f)(i);

除此之外还有很多强大的能力,你可以自己去看boost的DOC,现在我们来看一看他的实现原理:

Command模式是保存了一部分的参数的值,并在以后执行这个Command的时候使用。我们先来看看这是怎么实现的,也就是上面的传递的&Image::Draw, image1, rect1, SIMPLE_DRAW是如何保存的,他们是不同的型别,我只介绍一下他的基本原理,所以对代码会进行删改和润色,但是代码的设计原理是不会变的。

他大概的原理是这样的:

我们先来看看实际的bind函数,我仅用接受两个参数的bind函数示范。

template<class R, class F, class A1, class A2>
_bi::bind_t<R, F, typename _bi::list_av_2<A1, A2>::type> bind(F f, A1 a1, A2 a2)
{
        typedef typename _bi::list_av_2<A1, A2>::type list_type;
        return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2));
}

1。首先,我们把所有参数的值和一些占位符传给bind函数,bind函数把这些参数值保存在一个叫list_av_2的型别里,调用如下:

boost::bind(&kao, _1, 10.0f);

namespace _bi
{
//////////////////////////////////////////////////////////list_av_2
template<class A1, class A2> struct list_av_2
{
        typedef typename add_value<A1>::type B1;  //add_value是一个把原始型别提炼出来的类
        typedef typename add_value<A2>::type B2;
        typedef list2<B1, B2> type;
};
//////////////////////////////////////////////////////////add_value,实现原理就是模板偏特化
template<class T> struct add_value
{
    typedef value<T> type;
};
template<class T> struct add_value< value<T> >
{
    typedef value<T> type;
};
template<class T> struct add_value< reference_wrapper<T> >
{
    typedef reference_wrapper<T> type;
};
template<int I> struct add_value< arg<I> >
{
    typedef boost::arg<I> type;
};
template<int I> struct add_value< arg<I> (*) () >
{
    typedef boost::arg<I> (*type) ();
};
template<class R, class F, class L> struct add_value< bind_t<R, F, L> >
{
    typedef bind_t<R, F, L> type;
};
//////////////////////////////////////////////////////////
};
//////////////////////////////////////////////////////////arg,他的用处是占位,并为以后识别提供信息
namespace boost
{
template<int I> class arg { };
};
//////////////////////////////////////////////////////////统一名称
namespace
{
static boost::arg<1> _1;
static boost::arg<2> _2;
...
};
//////////////////////////////////////////////////////////

2。我们发现上面的list_av_2仅仅是做了一个转接的作用,他把真实的型别提炼出来并把他做成一个list2传给bind_t,现在通过把operator()把参数传给bind_t,如下:
int i = 0;
boost::bind(&kao, _1, 10.0f)(i);

namespace _bi
{
//////////////////////////////////////////////////////////type
template<class T> class type {};
//////////////////////////////////////////////////////////bind_t
template<class R, class F, class L>
class bind_t
{
public:
        bind_t(F f, L const & l): f_(f), l_(l) {}  //把函数和仿函数传递,以及参数的值列表传给bind_t

        typedef typename result_traits<R, F>::type result_type;  //获得优化并正确的返回值型别

        result_type operator()() //暴露给我们调用的接口
        {
                list0 a;
                return l_(type<result_type>(), f_, a);  
        }       
        //我们利用result_type来识别参数列表相同但返回值不同的函数,但是如果result_type很大,或者引用其他的资源的话
        //那么将付出很大代价,所以是用type<result_type>()来识别。
                                               
            result_type operator()() const //暴露给我们调用的接口
        {
                list0 a;
                return l_(type<result_type>(), f_, a);
        }
        ...A1就省略了
        template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) //暴露给我们调用的接口
        {
                list2<A1 &, A2 &> a(a1, a2);
                return l_(type<result_type>(), f_, a);
        }

        template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) const //暴露给我们调用的接口
        {
                list2<A1 &, A2 &> a(a1, a2);
                return l_(type<result_type>(), f_, a);
        }
        ...A1,A2,A3等
private:
        F f_;  //函数和仿函数
        L l_;  //listN的参数列表
};
//////////////////////////////////////////////////////////
};

而在operator()又创建一个list2,这个list2的实例通过新传进的参数值和以前的list2的实例的参数值进行合并,把原来的list2实例里所有_1,_2,..的地方替换成在operator()里传进的参数。这样就相当方便,这通过重载的operator[]来实现,我们通过接受不同型别的operator[]来识别不同位置的参数,也就是_1,_2,...和其他参数型别

我去掉了这个类里的一些代码,他们主要是这个主题无关的一些代码

namespace _bi
{
//////////////////////////////////////////////////////////value
template<class T> class value
{
public:
        value(T const & t): t_(t) {}

        T & get() { return t_; }
        T const & get() const { return t_; }
private:
        T t_;
};
//////////////////////////////////////////////////////////list2
template<class A1, class A2>
class list2
{
public:
        list2(A1 a1, A2 a2): a1_(a1), a2_(a2) {}  //在这一步把参数值保存进list2

        A1 operator[] (boost::arg<1>) const { return a1_; }
        A2 operator[] (boost::arg<2>) const { return a2_; }
        template<class T> T & operator[] (value<T> & v) const { return v.get(); }
        template<class T> T const & operator[] (value<T> const & v) const { return v.get(); }
        template<class T> T & operator[] (reference_wrapper<T> const & v) const { return v.get(); }
        ...                     
        template<class R, class F, class A> R operator()(type<R>, F f, A & a) const
        {
                return unwrap(f, 0)(a[a1_], a[a2_]);   //unwarp的目的是把真正的型别从一些包装型别里分离出来
        }
        ...
private:
        A1 a1_;
        A2 a2_;
};
//////////////////////////////////////////////////////////unwrap
template<class F> inline F & unwrap(F & f, long)  //利用偏特化
{
    return f;
}
template<class F> inline F & unwrap(reference_wrapper<F> & f, int) //利用偏特化
{
    return f;
}
template<class F> inline F & unwrap(reference_wrapper<F> const & f, int) //利用偏特化
{
    return f;
}
//////////////////////////////////////////////////////////
};

namespace _bi
{
//////////////////////////////////////////////////////////bind_t
template<class R, class F, class L>
class bind_t
{
public:
        bind_t(F f, L const & l): f_(f), l_(l) {}
        ...
        template<class A1> result_type operator()(A1 & a1)
        {
                list1<A1 &> a(a1);
                return l_(type<result_type>(), f_, a);
        }
        template<class A1, class A2>
        result_type operator()(A1 & a1, A2 & a2) const
        {
                list2<A1 &, A2 &> a(a1, a2);
                return l_(type<result_type>(), f_, a); //在这里,我们创建一个list2,然后把我们的参数传递给他,
                                                       //然后就是list2<A1, A2>:perator,然后就是unwrap(f, 0)(a[_1], a[_2])
                                                       //然后就unwrap(f, 0)(a1_, a2_)
        }
        ...
private:
        F f_;
        L l_;
};
//////////////////////////////////////////////////////////
};

你可能已经发现了,我们调用的bint_t的operator()的参数个数和list2的operator()的参数个数不一样,实际上他的流程是这样的:

boost::bind(&kao, _1, 10.0f);把函数和仿函数,以及参数传给bind_t里的一个list2,这个list2保存的值是:
a1_--->_1,a2_--->10.0f
然后boost::bind(&kao, _1, 10.0f)(i);把参数i通过operator(A1& a)传递给bind_t,这个时候又生成一个list1,他保存的值是:
a1_--->i
然后把这个list1传递通过list2的operator()给原来的list2,而list2在内部调用unwrap(f, 0)(a[a1_], a[a2_]);,这个a就是刚才传递给list2的list1,这个时候会调用相对参数i而调用list1<A1>::operator[](boost::arg<1>),这个实际上返回i。相对list2本身保存的参数a2_而调用list1<A1>::operator[](value<T> & v),这个实际上返回list2本身保存的参数a2_。从而使前面的调用实际上成为:
unwrap(f, 0)(i, 10.0f);

这就就达到了目的,也就是说我们可以向下面这样进行调用:

void kao(int i, int j, int k, float p) { }

int i = 0;
boost::bind(&kao, _1, _1, _1, 10.0f)(i);

上面的调用效果实际上是:
kao(i, i, i, 10.0f);


水平平庸,错误难免,请指正为谢           《解析boost》
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

作品发布|文章投稿|广告合作|关于本站|游戏开发论坛 ( 闽ICP备17032699号-3 )

GMT+8, 2025-5-16 16:41

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表