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);
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)
{
(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
{
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.