在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,包含本类感兴趣的那些消息,占用内存可想而知比先前使用虚函数的方法要小很多,但是迭代的搜寻操会比直接调用虚函数的效率要低。
#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,包含本类感兴趣的那些消息,占用内存可想而知比先前使用虚函数的方法要小很多,但是迭代的搜寻操会比直接调用虚函数的效率要低。