探讨C++中的Map映射机制

概述

从MFC到ATL,充斥着Map映射机制,似乎没有了这个Map机制,就玩不转啦。在WebBrower控件中,也存在着事件映射;在COM中,在IDispatch中也存在着自定义的函数映射。

以前,只要一谈到映射机制,总是让我闻风丧胆,退而求自保,暂且如此而已,记住就可以啦。现在想来,只要是跨不去过的坎,若没有认真面对和解决,那就永远无法逾越,成为心中永远的痛。最终,只能作茧自缚而唯唯诺诺。既然老天爷,又给了我一次机会,那我就好好抓住这次机会啦。

轰轰烈烈的开场白讲完了,让我们回归主题:“映射机制”

格式

Windows消息的Map格式

map代码,如下所示:

	BEGIN_MSG_MAP(CTestDialog)
		MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
		MESSAGE_HANDLER(WM_CLOSE, OnClose)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
	END_MSG_MAP()
其实是三个define所构成的,如下所示:

#define BEGIN_MSG_MAP(theClass) \
public: \
	BOOL ProcessWindowMessage(_In_ HWND hWnd, _In_ UINT uMsg, _In_ WPARAM wParam,\
		_In_ LPARAM lParam, _Inout_ LRESULT& lResult, _In_ DWORD dwMsgMapID = 0) \
	{ \
		BOOL bHandled = TRUE; \
		(hWnd); \
		(uMsg); \
		(wParam); \
		(lParam); \
		(lResult); \
		(bHandled); \
		switch(dwMsgMapID) \
		{ \
		case 0:

#define MESSAGE_HANDLER(msg, func) \
	if(uMsg == msg) \
	{ \
		bHandled = TRUE; \
		lResult = func(uMsg, wParam, lParam, bHandled); \
		if(bHandled) \
			return TRUE; \
	}


#define END_MSG_MAP() \
			break; \
		default: \
			ATLASSERT(FALSE); \
			break; \
		} \
		return FALSE; \
	}

 
 
 
 
从中我们可以看到,头文件的map宏,确实有3个define所构成,其本质是定义了一个ProcessWindowMessage函数。在
#define MESSAGE_HANDLER(msg, func) \
中,msg和func由用户选择,所以就暴露这2个参数。若有3个参数有用户选择的话,则肯定会暴露3个参数啦。

一般来说,最后一个参数是函数名称或函数地址,而它之前的参数一般都是它的参数。这样就解决了,只用一个宏,就可以解决所有相同个数和类型的输入参数,但不同操作的一般化函数调用,即程序中的映射机制。

通常情况下,映射一词有照射的含义,是一个动词。在数学上,映射则是个术语,指两个元素集之间元素相互“对应”的关系,名词;也指“形成对应关系”这一个动作,动词。

(摘自百度百科)

说白了,就是一种对应关系嘛,就这么简单,没有啥可说的。

模板类的Map格式

在头文件中,我们定义如下格式的映射关系:

BEGIN_XXX_MAP(CClassName)

    XXX_ENTRY(String1, Identify1, OnFunctionName1)

    XXX_ENTRY(String2, Identify2, OnFunctionName2)

    ... ...

    XXX_ENTRY(StringN, IdentifyN, OnFunctionNameN)

END_XXX_MAP()

那么,我们现在可以理解为OnFunctionName函数需要String和Identify这两个变量。由于有若个这样的XXX_ENTRY,那么就会有相应个函数,暂且成为函数容器。

这时我们就会想到两种情况来解释个函数容器:

一种是:上面所说的“Windows消息映射”,它只是将消息和函数进行一一对应,则程序更富有表现力,同时隐藏了不必要的代码。并且对应关系比较简单,就是一个类函数指针的代理。

另一种是:若个函数作为函数容器出现,以便在对应的模板类中对容器中的各个函数进行轮询,以便决定是否使用具有特定码的函数。它不再作为一个代理的角色出现,而更多地是扮演成员变量数组的角色出现。

这样做的好处是,让模板类可以更加灵活的处理这个数组,以便完成特定的处理效果。解放了数据和函数,分别进行了处理。咱们职责分明,秋毫无犯嘛,呵呵。

注意事项:

(1)定义GetMap()函数

它一般被const staic所修饰,其返回值为指向模板数组的一个指针;这样在函数中引用该GetMap时,只需要使用T::GetMap即可,因为它是静态函数啊!如下为保存和显示三个变量关系的结构体:

template<class T>
struct ST_XXX_ENTRY
{
	typedef void(T::*Function_Name)();
	LPSTR string;
	UINT identify;
	Function_Name func;

	static void ProcessFunc1()
	{
		//...
	}

	static void ProcessFunc2()
	{
		// ...
	}
};
结构体固然重要,但是这里更为重要的是展现三个变量关系之间的静态函数。


map的实例化代码如下所示:

#define BEGIN_XXX_MAP(theClassName)\
	static const ST_XXX_ENTRY<theClassName> * GetMap() \
	{ \
		static ST_XXX_ENTRY<theClassName> theMap[] = \
		{

#define  XXX_ENTRY(string, identify, func) \
			{ string, identify, &theClassName::func},\ // 此处应用了类函数指针的获取方法

#define  END_XXX_MAP() \
			{ NULL, 0, ST_XXX_ENTRY<theClassName>::Function_Name(NULL)} \
		} \
	}

(2)调用GetMap函数

在父类模板中,必然定义了如何使用GetMap中的函数映射关系。那么此时的调用,必然是直接使用T::GetMap()来获得静态容器的指针,然后对它进行遍历和筛选,以期获得我们想要点对应函数或对应函数上的处理结果。


非常棒,到这里,我们已经基本讲完了如何关联map和实例化map,以及变量在结构体中的定义。呵呵,感觉越写越有感觉,越写越明白里面map机制的奥秘在哪里已经如何外化出这个奥秘。

客户(界面)代码

客户代码是使用map宏的代码,它会继承一个模板类,而模板类所需要的实例类便是客户类,为什么会是这个样子呢?

其实原因很简单,因为此处模板类就是将公共函数提取出来,并且统一处理map宏中的转换关系,从而精简客户代码。而客户类完全可以按照客户所想定义的方式定义,想如何命名类名就如何命名类名,很自由。唯一要做的就是继承一下模板类,并且添加自己喜欢的对应关系即可,想用什么函数名就用什么函数名,想用什么id就用什么id,因为map的实例化只是引用函数指针,跟名字一点关系都没有。够爽了吧,一个“牛爽”。

可话又说回来,所有的模板类不正是可以容纳各中类而存在,并且统一化处理流程的嘛。原来,我们从实践中,再次感受到模板的优点,或者说它的使命:

(1)模板更有助于编写。我们只需创建类或函数的一个泛型版本,而不是手动创建专用化;

(2)模板是类型安全的。 由于模板操作的类型在编译时是已知的,因此编译器可以在发生错误之前执行类型检查;

(3)由于可通过模板直接提取信息,因此模板更易于理解。(当然是这样的,若仅仅查看模板的话,显得比较抽象,若通过模板来实例化一个对象后,则提取信息变得可视化,确实易于理解。)

这三个优点,我是从msdn上摘的,不过稍微润色了一下,使得主旨更加明晰(毕竟翻译e文,仁者见仁哦)。


到此,我已经讲完了映射机制,Windows的所有映射机制,大抵如此,照葫芦画瓢。


真没有想到,居然写了这么多。不过真心体会,写完这篇blog之后,感觉对映射机制如释重负,感觉从未有过的轻松自在。越发觉得,写blog是一个很不错的深入学习的体验。只有在写得过程中,才会感受到那种顺藤摸瓜的感觉。


你可能感兴趣的:(探讨C++中的Map映射机制)