Simulating Multiple Inheritance Under MFC by Using C++ Templates

Environment: VC6

This article explains how multiple inheritance (MI) can be used in MFC (Microsoft Foundation Classes library) applications, why MFC does not support MI, and how to overcome this limitation by using C++ templates.

If you are writing object oriented-code, you frequently use a concept of inheritance by creating more specialized objects (components) from more general ones. For example, if you want to add some new feature to the CDialog class, you create a new class, CMyDialog, by subclassing CDialog, and implement the feature by adding and/or redefining some of its methods.

However, sometimes you need to subclass two or more classes at a time. For example, imagine that two programmers created two
specialized dialogs, CJohnsDialog and CBobsDialog, by subclassing CDialog, each adding a new feature, and you want to have a class that supports both features. This pattern occurs very frequently, especially in open-source communities such as CodeGuru. It would be nice if you could create a class that subclasses both CJohnsDialog and CBobsDialog. Unfortunately, you cannot do it that simply.

Although the C++ language supports multiple inheritance, MFC does not. This is because the polymorphism of MFC objects is provided not only by late binding mechanism (which supports MI), but also by cascading message maps technique, inherited from older versions of MS Windows. This technique works as follows: If an object is sent a message, it tries to look up a corresponding message handler in its message map to process the message. If the search fails, te message map of the superclass is searched, and so forth. When a class has multiple superclasses, it is not clear to which superclass a message handling should be delegated.

So, if you want to integrate two independent classes into your own class, you usually need to subclass one of them (perhaps the more complex one), and to manually re-implement the other feature in the derived class. Similarly, if you want to implement some general feature to classes that subclass from different superclasses, you need to re-implement the feature for each subclass.

I propose an elegant approach to automate this procedure. If you are writing some specific component that adds some feature to some MFC or derived class, you wouldn’t implement it as a class with specific superclass, but as a C++ template. The template has exactly one argument that is its superclass. Then, you can generate classes that implement the new feature and have a specific superclass by simply instantiating the template.

The feature template looks like this:


template <class BASE> class CCoolFeature: pubic BASE {
/* implement the feature as if this were an ordinary class,
using BASE as a keyword for superclass */

};

Then, we can generate as many classes as we need by instantiating the template with different arguments (superclasses). Below, we introduced aliases denoting classes implementing the feature, while subclassing the specified MFC superclass.


typedef CCoolFeature<CEdit> CCoolEdit;
typedef CCoolFeature<CDialog> CCoolDialog;
typedef CCoolFeature<CJohnsDialog> CCoolJohnsDialog;

However, the implementation of a “templatized” feature is somehow more difficult than the implementation of an ordinary class. You should be aware of the following pitfalls:

  • Everything related to the feature, including its implementation, must be in the include file (or in a file included by it). If you want to write an implementation of a method that is not inline, you must do it as follows:
  • template <class BASE> int CCoolFeature<BASE> methodImpl()
       ...
    }
    
  • The implementation of the message map provided by MFC’s BEGIN_MESSAGE_MAP / END_MESSAGE_MAP macros needs special treatment. These macros are defined in the afxwin.h header. In particular, the BEGIN_MESSAGE_MAP is defined as follows:
  • #ifdef _AFXDLL
    #define BEGIN_MESSAGE_MAP(theClass, baseClass) 
      const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() 
        { return &baseClass::messageMap; } 
      const AFX_MSGMAP* theClass::GetMessageMap() const 
        { return &theClass::messageMap; } 
      AFX_COMDAT AFX_DATADEF const AFX_MSGMAP
                 theClass::messageMap = 
      { &theClass::_GetBaseMessageMap,
        &theClass::_messageEntries[0] }; 
      AFX_COMDAT const AFX_MSGMAP_ENTRY
                 theClass::_messageEntries[] = 
      { 
    
    #else
    #define BEGIN_MESSAGE_MAP(theClass, baseClass) 
      const AFX_MSGMAP* theClass::GetMessageMap() const 
        { return &theClass::messageMap; } 
      AFX_COMDAT AFX_DATADEF const AFX_MSGMAP
                 theClass::messageMap = 
      { &baseClass::messageMap,
        &theClass::_messageEntries[0] }; 
      AFX_COMDAT const AFX_MSGMAP_ENTRY
                 theClass::_messageEntries[] = 
      { 
    
    #endif
    

    We see that the macro defines implementation of a couple of methods, and starts the definition of the message map. Because the method implementation needs to be templatized too, we need to redefine the macro; for example, as follows:

    #ifdef _AFXDLL
    #define BEGIN_MESSAGE_MAP_T(theClass) 
      template <class BASE> const AFX_MSGMAP*
               PASCAL theClass<BASE>::_GetBaseMessageMap() 
        { return &BASE::messageMap; } 
      template <class BASE> const
               AFX_MSGMAP*
               theClass<BASE>::GetMessageMap() const 
        { return &theClass<BASE>::messageMap; } 
      template <class BASE> AFX_COMDAT AFX_DATADEF
               const AFX_MSGMAP
               theClass<BASE>::messageMap = 
      { &theClass<BASE>::_GetBaseMessageMap,
        &theClass<BASE>::_messageEntries[0] }; 
      template <class BASE> AFX_COMDAT
               const AFX_MSGMAP_ENTRY
               theClass<BASE>::_messageEntries[] = 
      { 
    
    #else
    #define BEGIN_MESSAGE_MAP_T(theClass<BASE>, BASE) 
      template <class BASE>
               const AFX_MSGMAP*
               theClass<BASE>::GetMessageMap() const 
        { return &theClass<BASE>::messageMap; } 
      template <class BASE>
               AFX_COMDAT AFX_DATADEF
               const AFX_MSGMAP theClass<BASE>::
                     messageMap = 
      { &BASE::messageMap,
        &theClass<BASE>::_messageEntries[0] }; 
      template <class BASE> AFX_COMDAT
               const AFX_MSGMAP_ENTRY theClass<BASE>::
                     _messageEntries[] = 
      { 
    
    #endif
    

    Note that the BEGIN_MESSAGE_MAP_T macro has only one argument: The base class is substituted automatically with the BASE argument of the feature template.

Having this in mind, I recommend the following procedure when implementing a new feature:

  1. Subclass some applicable MFC class (such as CWnd) and implement the new feature into it. You can use all the features of the Developer Studio (for example, Class Wizard).
  2. Debug and test the new component.
  3. Templatize the component as described above.
  4. If you want to modify the feature implementation later, the support of Developer Studio would be limited, because it would be confused by the templatization.

Finally, I would like to demonstrate this concept with a simple example. Imagine you have a project with many dialogs and property pages, perhaps borrowed from other programmers, and you want to add a context help to them. Then, you need to add a help button for each, and provide a handler for it. This can be done manually, or implemented as a feature template, and added to each dialog using template instantiation.

The latter technique leads to the following implementation:

#if !defined(HELP_DIALOG_H)
#define HELP_DIALOG_H

#include "mit.h"

template <class BASE, int CODE> class THelpDialog :
         public BASE
{
public:
  THelpDialog(CWnd* pParent = NULL);

protected:

  afx_msg void OnHelp();
  DECLARE_MESSAGE_MAP()
};

template <class BASE, int CODE>
         THelpDialog<BASE,CODE>::THelpDialog(CWnd* pParent)
  : BASE(pParent)
{
}

template <class BASE, int CODE>
         void THelpDialog<BASE,CODE>::OnHelp()
{
  CString s;
  s.Format("Displaying help page with code %i", CODE);
  AfxMessageBox(s);
}

BEGIN_MESSAGE_MAP_T2(THelpDialog, int, CODE)
ON_BN_CLICKED(IDHELP, OnHelp)
END_MESSAGE_MAP()

#endif

This file implements the feature template for a help dialog. It has one more argument that denotes the help code of the dialog. This argument will be substituted with some constant integer during template instantiation.

The “mit.h” header is an auxiliary header with the definition of the BEGIN_MESSAGE_MAP_T and BEGIN_MESSAGE_MAP_T2 macros. The latter macro is a simple extension of the former for the case the feature template has more arguments.

In this simple example, the help button handler implementation simply displays a message box with the help code of the dialog the help button of which has been pressed.

This simple feature is applied to two dialogs, CFirstDialog and CSecondDialog, as follows:

void CMainFrame::OnDialogFirstdialog()
{
  THelpDialog<CFirstDialog, 11111> dlg;

  dlg.DoModal();
}

void CMainFrame::OnDialogSeconddialog()
{
  THelpDialog<CSecondDialog, 22222> dlg;

  dlg.DoModal();
}

Downloads


Download demo project – 23 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read