Creating a Practical C++ Template

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Environment: VC6, VC.Net, Win32

Introduction

Most C++ books and articles love to use containers or mathematical functions (like compile time recursion) to represent the power of the template. Yeah, it is cool, but it seems only a scientific programmer can benefit from meta programming. In this article, I’ll show how an application developer can enjoy the fun of programming templates, too.

  1. Remove code duplication.

    Imagine I build a MFC dialog box that has two different kinds of button, a CBitmapButton and normal CButton; I use two CArray instances, respectively, to hold them. Therefore, in the .h file, I’ll have the following declaration:


    #include<afxtempl.h>
    class MyDialog
    {
    //….
    private:
    CArray<CButton,CButton&> m_buttonArray;
    CArray<CBitmapButton,CBitmapButton&> m_bmpButtonArray;
    };

    Now, I need a function to hide all the buttons on the dialog box. The first iteration needed to write the function is:


    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 names, but it smells bad for a real programmer who thinks programming is more than a job but also is an art. You can imagine that, if there are 10 different types of button arrays, I have to use Ctrl+C and Ctrl+V 10 more times; it sucks. There must be something that can be done, and now the template kicks in.

    Let’s add one more template function to the MyDialog; let’s call it ShowButtons. Now, the .h file becomes:

    class MyDialog
    {
    //....
    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;
    };
    

    We’ll define it as follows (there is 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,BOOL bShow)
    {
    for (int nIndex=0;nIndex<rButtonArray.GetSize();++nIndex)

    {

    rButtonArray[nIndex].ShowWindow(bShow);

    }
    }

    and rewrite the ShowAllButtons as follows:


    void
    CMyDialog::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 an explicit type specified, as in:


    ShowButtons<CButton>(m_buttonArray,bShow);
    ShowButtons<CBitmapButton>(m_bmpButtonArray,bShow);

    These 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 is a chance that you will have to supply a C-style callback function to a Win32 API (like CreateThread, SetTimer, and so forth). If the callback function required has a LPVOID as an argument, everything is okay. Using the old trick passes this pointer as an LPVOID argument. But, unfortunately, there is an API called SetWinEventHook; its 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. Its 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; hence, there is no way to pass this pointer. It is virtually impossible to access the non-static data member inside the WinEventProc function. That is horrible, and a nightmare for an OO developer.

    To solve this problem, just recall an old golden software development rule, “Many design problems can be solved by one more indirection.” So, I created a template class named WinEvent. It 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,
                           PfnCallback pfn,UINT eventMin,UINT
    eventMax,
                           HMODULE ModWinEventProc,DWORD
    idProcess
                           DWORD dThread,UINT dwFlags);
      private:
    
      static WinEventHandler *s_pHandler;
      static PfnCallback s_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,
          PfnCallback pfn
          UINT eventMin,UINT eventMax,HMODULE hModWinEventProc,
          DWORD idProcess,DWORD idThread,UINT dwFlags)
    {
      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,aidChild,dwEventThread,
                      dwmsEventTime);
    }
    

    As mentioned above, SetWinEventHook takes only a 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:

    class MyDialog
    {
    //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 WinEvent class doesn’t have to be a template. It can hold a “hardcoded” EventHandler type, but to reduce the de-coupling, a 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 shows the practical use of a template and I believe meta-programming has a growing impact in application development setting, too. Please enjoy.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read