Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Practical uses for C++ templates

0.00/5 (No votes)
24 Aug 2002 2  
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here