Practical uses for C++ templates






3.33/5 (9 votes)
Aug 25, 2002
3 min read

75931
Two examples of practical usage of C++ templates in application development
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.