消息映射深度探索

消息映射深度探索(1):基础面

消息映射这项技术,给消息横向(多个类对象)或纵向(继承树)流动的机会。

 

 

此技术最初之发展动机是为了简化win32程序的消息处理过程,后来也被应用到网络程序的消息处理。一旦某个消息到达win32消息处理函数,此函数就会 使用一个switch...case映射网将此消息映射至相应处理函数。switch...case具有与生俱来的缺点:难于扩展,效率低下。随着消息的 增多,switch...case将变得庞大而不堪重负。消息映射可以解决这个问题,因为当对象运用了消息映射技术后,对象自己就能够处理消息(或者说对外界宣告,我能处理这个消息,请把它交给我处理)。

 

消息映射架构在一大堆有趣的细节之上。在深入到细枝末节前,先让我们看下基础面:传统的不使用消息映射程序是如何实作的?下面是一种可能:

void HandleMessage( Message* message ) {

    switch ( message->GetID() ) {

        case MessageID_A:

            FunctionA( message );

            break;

        ...

        case MessageID_N:

            FunctionN( message );

            break;

        default:

            assert( false && "Unknown message" );

    }

}

 

这个做法几乎不具备任何扩展性,如果HandleMessage()被封装到库中那就意味着用户不再有任何修改的机会,那么用户该如何针对自己喜爱的消息做特殊处理,而忽略那些不打算处理的消息呢?

 

继续前行之前,我们先来了解几个将被使用的宏:

 

#include <iostream>

 

#define PRINT( msg ) { std::cout << msg << std::endl; }

#define FUNC( func ) void func() { PRINT( #func##"()" ); }

#define MFUNC( class, func ) void func() { PRINT( #class##"::"###func##"()" ); }

#define VMFUNC( class, func ) virtual MFUNC( class, func )

 

namespace AutoTest {

    FUNC( func );

 

    struct TestStruct {

        MFUNC( TestStruct, func );

        VMFUNC( TestStruct, vfunc );

    };

   

    void TestPRINT() {

        PRINT( "PRINT()" );

        func();

        TestStruct t;

        t.func();

        t.vfunc();

    }

}

 

void main() {

    AutoTest::TestPRINT();

}

 

这里列出了一些宏,用于打印或者产生一些简单的全局打印函数或者成员函数。

 

消息映射深度探索(2):MFC的做法 - 使用消息映射表

MFC被成功做出来之前,一个最简单的想法就是使用虚函数,为每个消息定制一个虚函数,下面是个可能的实现:

 

#include <iostream>

#include <map>

#include <cassert>

 

#define PRINT( msg ) { std::cout << msg << std::endl; }

#define FUNC( func ) void func() { PRINT( #func##"()" ); }

#define MFUNC( class, func ) void func() { PRINT( #class##"::"###func##"()" ); }

#define VMFUNC( class, func ) virtual MFUNC( class, func )

 

enum MsgID {

    MsgID_1,

    MsgID_2,

};

 

namespace PreMFC {

    class Wnd;

    typedef void (Wnd::*MemberFunc)();

    typedef std::map< int, MemberFunc > MsgRegistry;   

 

    struct MsgMap {

        const MsgRegistry& thisMsgRegistry;

        const MsgMap* baseMsgMap;

    };

 

    class Wnd {

    public:

        void HandleMsg( int id ) {

            switch( id ) {

                case MsgID_1: Handle1(); break;

                case MsgID_2: Handle2(); break;

                default: break;

            }

        }

    private:

        VMFUNC( Wnd, Handle1 );

        VMFUNC( Wnd, Handle2 );

    };

 

    class MyWnd : public Wnd {

    private:

        VMFUNC( MyWnd, Handle1 );

    };

}

 

namespace AutoTest {

    void TestPreMFCMsgMap() {

        using namespace PreMFC;

        Wnd* wnd = new MyWnd;

        wnd->HandleMsg( MsgID_1 );

        wnd->HandleMsg( MsgID_2 );

    }

}

 

void main() {

    AutoTest::TestPreMFCMsgMap();

}

 

不幸的是,这个做法太耗内存,每个类都将有一个和基类一模一样的庞大的vtable,此table内含所有虚函数指针。

下面我们看下MFC是如何实现消息映射的:

 

...

 

namespace MFC {

    class Wnd;

    typedef void (Wnd::*MemberFunc)();

    typedef std::map< int, MemberFunc > MsgRegistry;   

 

    struct MsgMap {

        const MsgRegistry& thisMsgRegistry;

        const MsgMap* baseMsgMap;

    };

 

    class Wnd {

    public:

        void HandleMsg( int id ) {           

            const MsgMap* msgMap = &GetMsgMap();

            assert( msgMap != NULL );

 

            while ( msgMap != NULL ) {               

                const MsgRegistry& registry = msgMap->thisMsgRegistry;

                MsgRegistry::const_iterator it = registry.find( id );

                if ( it != registry.end() ) {

                    ( this->*( it->second ) )();

                    break;

                }

                msgMap = msgMap->baseMsgMap;

            }           

        }

 

    protected:

        virtual const MsgMap& GetMsgMap() const {

            static MsgMap msgMap = {

                GetInitilizedMsgRegistryWnd(),

                NULL

            };

            return msgMap;

        }

 

    private:

        MsgRegistry& GetInitilizedMsgRegistryWnd() const {

            static MsgRegistry thisMsgRegistry;

            thisMsgRegistry.insert( std::make_pair( MsgID_1, &Wnd::Handle1 ) );

            thisMsgRegistry.insert( std::make_pair( MsgID_2, &Wnd::Handle2 ) );

            return thisMsgRegistry;

        }

 

    private:

        MFUNC( Wnd, Handle1 );

        MFUNC( Wnd, Handle2 );

    };

 

    class MyWnd : public Wnd {

    protected:

        virtual const MsgMap& GetMsgMap() const {

            static MsgMap msgMap = {

                GetInitilizedMsgRegistryMyWnd(),

                &Wnd::GetMsgMap()

            };

            return msgMap;

        }

    private:

        MsgRegistry& GetInitilizedMsgRegistryMyWnd() const {

            static MsgRegistry thisMsgRegistry;

            thisMsgRegistry.insert( std::make_pair( MsgID_1, (MemberFunc)&MyWnd::Handle1 ) );           

            return thisMsgRegistry;

        }

 

    private:

        MFUNC( MyWnd, Handle1 );

    };

}

 

namespace AutoTest {

    void TestMFCMsgMap() {

        using namespace MFC;

        Wnd* wnd = new MyWnd;

        wnd->HandleMsg( MsgID_1 );

        wnd->HandleMsg( MsgID_2 );

    }

}

 

void main() {

    AutoTest::TestMFCMsgMap();

}

 

原理

 

1. 建立消息映射表

每个类以GetInitilizedMsgRegistry##Class()产生一个static映射表,并与基类之映射表做链接(GetMsgMap()内)

 

2. 迭代查找消息函数

消息映射起源于Wnd::HandleMessage函数,此函数将在整个继承链的消息映射网中搜寻id对应之处理函数,首先从叶端找起(Template模式),一直到根端。

这是个迭代过程(下面我们还会讲到以递归实现的消息映射,但那要依赖于虚函数),速度很快。

 

细节

 

1. 看起来MyWnd::Handle1重写了Wnd::Handle1,这与EffectiveC++的建议相悖(不该重写非虚函数)。但这里是个例外,因为两个名字只是碰巧相同罢了(你完全可以用其他名字)

2. 不同于MFC的是,直接使用map来存储映射表而非数组(MFC的真正实现),以简化搜寻操作(但是内存占用多了)。(MFC的搜寻操作为AfxFindMessageEntry,其内部不知使用什么技巧来加速查询)

3. 可以把派生类成员函数指针转换为基类成员函数指针,反过来不成立。(MemberFunc)&MyWnd::Handle1;

 

优缺点

 

使用消息映射表将会为每个类产生一个map,包含本类感兴趣的那些消息,占用内存可想而知比先前使用虚函数的方法要小很多,但是迭代的搜寻操会比直接调用虚函数的效率要低。

 

消息映射深度探索(3):使用宏简化操作

消息映射网的建立可以用宏定义出来,以简化派生类的操作:

 

#include <iostream>

#include <map>

#include <cassert>

 

#define PRINT( msg ) { std::cout << msg << std::endl; }

#define FUNC( func ) void func() { PRINT( #func##"()" ); }

#define MFUNC( class, func ) void func() { PRINT( #class##"::"###func##"()" ); }

#define VMFUNC( class, func ) virtual MFUNC( class, func )

 

enum MsgID {

    MsgID_1,

    MsgID_2,

};

 

namespace MFCMacro {

    class Wnd;

    typedef void (Wnd::*MemberFunc)();

    typedef std::map< int, MemberFunc > MsgRegistry;    

 

    struct MsgMap {

        const MsgRegistry& thisMsgRegistry;

        const MsgMap* baseMsgMap;

    };

 

/* MACRO BEGIN *///

#define MSG_BEGIN( theClass, baseClass )                        /

protected:                                                      /

    virtual const MsgMap& GetMsgMap() const {                   /

        static MsgMap msgMap = {                                /

            GetInitilizedMsgRegistry##theClass(),               /

            &baseClass::GetMsgMap()                             /

        };                                                      /

        return msgMap;                                          /

    }                                                           /

private:                                                        /

    MsgRegistry& GetInitilizedMsgRegistry##theClass() const {   /

        static MsgRegistry thisMsgRegistry;                

 

#define MSG_ENTRY( id, func )                                   /

        thisMsgRegistry.insert( std::make_pair( id, (MemberFunc)func ) );

 

#define MSG_END()                                               /

        return thisMsgRegistry;                                 /

    }

/* MACRO END *

 

    class Wnd {

        ...   

    };

 

    class MyWnd : public Wnd {

        MSG_BEGIN( MyWnd, Wnd )

            MSG_ENTRY( MsgID_1, &MyWnd::Handle1 )           

        MSG_END()

    private:

        MFUNC( MyWnd, Handle1 );       

    };   

}

 

namespace AutoTest {

    void TestMFCMacroMsgMap() {

        using namespace MFCMacro;

        Wnd* wnd = new MyWnd;

        wnd->HandleMsg( MsgID_1 );

        wnd->HandleMsg( MsgID_2 );

    }

}

 

void main() {

    AutoTest::TestMFCMacroMsgMap();

}

 

注意

 

为了表述方便,上面所示的宏直接将消息映射的定义部分放在类声明中。

你也可以像MFC那样将声明和实现分开:

 

声明

DECLARE_MESSAGE_MAP()

 

实现

BEGIN_MESSAGE_MAP()

 ON_WM_XXX()

 ON_COMMAND()

 ON_MESSAGE()

 ...

END_MESSAGE_MAP()

 

消息映射深度探索(4):职责链模式 - 使用虚函数替换映射表

下面我们将介绍另一种实现消息映射的方式:职责链模式

 

#include <iostream>

#include <map>

#include <cassert>

 

#define PRINT( msg ) { std::cout << msg << std::endl; }

#define FUNC( func ) void func() { PRINT( #func##"()" ); }

#define MFUNC( class, func ) void func() { PRINT( #class##"::"###func##"()" ); }

#define VMFUNC( class, func ) virtual MFUNC( class, func )

 

enum MsgID {

    MsgID_1,

    MsgID_2,

};

 

namespace COR { // chain of responsibility

    class Wnd;

    typedef void (Wnd::*MemberFunc)();   

 

    class Wnd {

    public:

        virtual void HandleMsg( int id ) {

            switch ( id ) {

                case MsgID_1: Handle1(); break;

                case MsgID_2: Handle2(); break;

            }

        }

 

    private:

        MFUNC( Wnd, Handle1 );

        MFUNC( Wnd, Handle2 );

    };

 

    class MyWnd : public Wnd {

    public:

        virtual void HandleMsg( int id ) {

            switch( id ) {

                case MsgID_1: Handle1(); break;

                default: Wnd::HandleMsg( id );

            }

        }

 

    private:

        MFUNC( MyWnd, Handle1 );

    };   

}

 

namespace AutoTest {

    void TestCORMsgMap() {

        using namespace COR;

        Wnd* wnd = new MyWnd;

        wnd->HandleMsg( MsgID_1 );

        wnd->HandleMsg( MsgID_2 );

    }

}

 

void main() {

    AutoTest::TestCORMsgMap();

}

 

原理

 

不需要消息映射表,而是利用虚函数HandleMsg()向父类递归查找,详情可参考《设计模式》职责链模式。

 

细节

 

1. 可以把switch...case用查找表替换掉,但绝非必要。通常使用此法是在有封装之必要时才会采取的措施,详情可参考

http://blog.csdn.net/hjsunj/archive/2008/01/07/2028597.aspx

http://blog.csdn.net/hjsunj/archive/2008/01/11/2037354.aspx

 

2. 使用NVI模式(《Effective C++ 3rdchapter35)让思路更加清晰,此时需借助查找表

HandleMsg变为非虚函数:

void HandleMsg( int id ) {

    MemberFunc func = Lookup( id );

    if ( func != NULL ) {

        (this->*func)();

   }

}

virtual MemberFunc Lookup( int id ) {

    // 如果thisclassid之处理函数,返回之  

    // 否则返回BaseClass::Lookup( id );

}

 

优缺点

 

1. 实现简单:不再需要定制宏,不再需要建立消息映射表,所需的只是重写虚函数。

2. 节省内存:由于不再需要为每个派生类准备查找表,节省内存是自然而然的事。

2. 效率低下: 通常使用虚函数的效率会高于查找表(《设计模式》p204),但那只对一次调用成立。职责链模式对虚函数递归调用,其时间开销必然要高于映射表方案的迭代查找。

 

总结

孰优孰劣

 

上面我们讨论了实现消息映射的两种方案:

 

1. 使用消息映射表

2. 使用虚函数

 

在效率上我们可以看出前者胜出,内存使用上后者胜出(前提是后者不使用查找表替换switch...case)。

所以看其来大家各有优劣。

 

使用消息映射表必然要使用宏,除非事先这些宏已经制作完成,否则如果写这样的宏必然是件非常痛苦的事情。所以消息映射表的方式通常被用在Framework的实作,比如MFC等。

 

而虚函数的制作相当方便,所以如果不是制作Framework的话,那么这个方案也是不错的。

 

更进一步的讨论:允许消息横流

 

作为Framework的实作者,你或许还需要让消息拥有横流的机会,即流向子系统而非父类。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值