转载美文(讲解template)in pure english

forrest2001 2002-08-27 11:46:06
Introduction
Most of the C++ books and articles love to use a container or mathematical function (like compile time recursion) to demonstrate the power of the template. Yeah it is cool, but it seems only scientific programmer can benefit from meta programming. In this article I'll show how the application developer can enjoy the fun of template programming too.

1. Remove code duplication
Imagine I build a MFC dialog box which has two different kinds of buttons, a CBitmapButton and normal CButton, and I use two CArrays to hold them respectively. Therefore, in the header file I'll have the following declaration

#include<afxtempl.h>
class MyDialog
{
//....
private:
CArray<CButton,CButton&> m_buttonArray;
CArray<CBitmapButton,CBitmapButton&> m_bmpButtonArray;
};
and now I need a function to hide all the buttons on the dialog box. The first thought is to write the function:

void MyDialog::ShowAllButtons(BOOL bShow)
{
int nIndex=0;
for (nIndex=0;nIndex < m_buttonArray.GetSize();++nIndex)
{
m_buttonArray[nIndex].ShowWindow(bShow);
}

for (nIndex=0;nIndex<m_bmpButtonArray.GetSize();++nIndex)
{
m_bmpButtonArray[nIndex].ShowWindow(bShow);
}
}
It seems what I need is to copy and paste and change the variable name but it smells bad for a real programmer who thinks programming is more than a job but also an art. You can imagine if there are 10 different types of button array that I will have to Ctrl+C and Ctrl+V 10 more times, it sucks. There must be something can be done, and this is where templates kick in.

Add one more template function to the MyDialog, let's call it ShowButtons, now the .h file becomes

class CMyDialog
{
//....

private:
void ShowAllButtons(BOOL bShow);

//new template function
template <typename ButtonType>
void ShowButtons(CArray<ButtonType,ButtonType&> &rButtonArray, BOOL bShow);

private:

CArray<CButton,CButton&> m_buttonArray;
CArray>CBitmapButton,CBitmapButton&> m_bmpButtonArray;
};
and define it as follows (there still no export keyboard supported in VC++ yet, so I defined the function in the same .h file)

template <typename ButtonType>
void CMyDialog::ShowButtons(CArray<ButtonType,ButtonType&> &rButtonArray,BOOLbShow)
{
for (int nIndex=0;nIndex<rButtonArray.GetSize();++nIndex)
{
rButtonArray[nIndex].ShowWindow(bShow);
}
}
and rewrite the ShowAllButtons as follows

voidCMyDialog::ShowAllButtons(BOOL bShow)
{
ShowButtons(m_buttonArray,bShow);
ShowButtons(m_bmpButtonArray,bShow);
}
the compiler will deduce the type automatically, you are also welcome to call the ShowButtons with the explicit type specified, like

ShowButtons<CButton>(m_buttonArray,bShow);
ShowButtons<CBitmapButton>(m_bmpButtonArray,bShow);
both work, and now there is no need to copy and paste anymore because the code is generated by the compiler through the template. This is the power of C++ versus VB or any other language.

2. Generic callback
When programming windows with C++ there will be a chance that you have to supply a C style callback function to a Win32 API (like CreateThread, SetTimer.etc). If the callback function required has a LPVOID as an argument, then everything is ok, using the old trick that pass the this pointer as LPVOID argument. But, unfortunately, there is an API called SetWinEventHook, and it's prototype is,

HWINEVENTHOOK WINAPI SetWinEventHook(
UINT eventMin,
UINT eventMax,
HMODULE hmodWinEventProc,
WINEVENTPROC lpfnWinEventProc,
DWORD idProcess,
DWORD idThread,
UINT dwflags
);
which takes a callback function, WINEVENTPROC and it's function signature is

VOID CALLBACK WinEventProc(


HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime
);
Obviously, there is no LPVOID parameter, and hence, there is no way to pass the this pointer, so there is virtually impossible to access the non-static data member inside the WinEventProc function. That is horrible and a nightmare for a OO developer.

To solve this problem just recall an old golden software development rule, "many design problem can be solved by one more indirection" so I create a template class named WinEvent that has the following structure.

template <typename WinEventHandler>
class WinEvent
{
public:

typedef VOID (CALLBACK WinEventHandler::*PfnCallback)
(HWINEVENTHOOK,DWORD,HWND,LONG,LONG,DWORD,DWORD);


static HWINEVENTHOOK InstallWinEventHook(
WinEventHandler *pHandler,
PfnCallbackpfn,
UINT eventMin,UINTeventMax,HMODULEhModWinEventProc,
DWORD idProcess,DWORDidThread,UINTdwFlags);

private:


static WinEventHandler *s_pHandler;
static PfnCallbacks _pfnCallback;

private:

WinEvent()
~WinEvent();

static VOID CALLBACK WinEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime);
};

template <typename WinEventHandler>
HWINEVENTHOOK WinEvent<WinEventHandler>::InstallWinEventHook(
WinEventHandler *pHandler,
PfnCallbackpfn
UINT eventMin,UINTeventMax,HMODULEhModWinEventProc,
DWORD idProcess,DWORDidThread,UINTdwFlags)

{
s_pHandler=pHandler;
s_pfnCallback=pfn;

return SetWinEventHook(eventMin,eventMax,
hModeWinEventProc,WinEventProc,
idProcess,idThread,dwFlags);
}


template <typename WinEventHandler>
VOID CALLBACK WinEvent<WinEventHandler>::WinEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime)
{
//delegate to the WinEventHandler class's function
(s_pHandler->*s_pfnCallback)(hWinEventHook,event,hwnd,idObject,
idChild,dwEventThread,dwmsEventTime);

}

as mentioned above, SetWinEventHook only takes c-style callback so I made WinEventProc a static function. To access s_pHandler and s_pfnCallback inside the WinEventProc, I had no choice but made them static too. And now, if my MyDialog class wants to receive the callback, I need to add a member callback function. The new .h declaration becomes

classMyDialog
{
//same as the above
private:

VOID CALLBACK WinEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD dwEventThread,
DWORD dwmsEventTime);
};
the function name doesn't need to be WinEventProc, it can be anything you like, as long as the function signature is correct. The following statement will call the InstallWinEventHook

WinEvent<CMyDialog>::InstallWinEventProc(this,WinEventProc,,,,,);
Conclusion
Technically, the class WinEvent doesn't need to be a template. It can hold a "hardcoded" EventHandler type but to reduce the de-coupling, template is the only choice.

I know the WinEvent class is unfinished and there is plenty of space to improve but the above design just to show the practical use of template and I believe meta-programming has growing impact in application development too. Please enjoy.

About kode tekno
...全文
60 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

16,472

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧