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


Comments

  • MI

    Posted by Legacy on 04/15/2003 12:00am

    Originally posted by: Per

    Ive said it before and I'll say it again:

    Use multiple inheritance with caution!

    Reply
  • This does not compile under vc7

    Posted by Legacy on 04/06/2003 12:00am

    Originally posted by: Mark

    This does not compile under vc7
    

    Reply
  • With more than one template param

    Posted by Legacy on 04/03/2003 12:00am

    Originally posted by: Horst

    this versions works with more than one template parameter but it's not really nice. The difference is the definition of the MM_ macros before using the BEGIN_MESSAGE_MAP_T. Using predefined macros instead of the text directly seems to help the preprocessor in accepting the parameters.

    Horst

    #ifdef _AFXDLL
    #define BEGIN_MESSAGE_MAP_T(tparam, theClass, baseClass) \
    template tparam \
    const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
    { return &baseClass::messageMap; } \
    template tparam \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
    { return &theClass::messageMap; } \
    template tparam \
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
    { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \
    template tparam \
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
    { \

    #else
    #define BEGIN_MESSAGE_MAP_T(tparam, theClass, baseClass) \
    template tparam \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
    { return &theClass::messageMap; } \
    template tparam \
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
    { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
    template tparam \
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
    { \

    #endif

    template <class BASE, int n> class TWnd : public BASE
    {
    public:
    TWnd();

    protected:

    afx_msg void OnPaint();
    DECLARE_MESSAGE_MAP()
    };

    template <class BASE, int n> TWnd<BASE, n>::TWnd()
    {
    }

    template <class BASE, int n> void TWnd<BASE, n>::OnPaint()
    {
    CPaintDC dc(this); // device context for painting

    CRect rc;
    GetClientRect(&rc);
    dc.FillSolidRect(&rc, RGB(255, 0, 0));

    ReleaseDC(&dc);
    }

    #define MM_TPARAM <class BASE, int n>
    #define MM_THECLASS TWnd<BASE, n>
    #define MM_BASE BASE

    //BEGIN_MESSAGE_MAP_T(<class BASE, int n>, TWnd<BASE, n>, BASE)
    BEGIN_MESSAGE_MAP_T(MM_TPARAM, MM_THECLASS, MM_BASE)
    ON_WM_PAINT()
    END_MESSAGE_MAP()

    #undef MM_TPARAM
    #undef MM_THECLASS
    #undef MM_BASE

    class CMyWnd : public TWnd<CStatic, 0>
    {
    public:
    CMyWnd() {
    }
    };

    Reply
  • little improvement!?

    Posted by Legacy on 04/02/2003 12:00am

    Originally posted by: Horst

    to avoid lots of macros for a variable number of parameters i changed the code a little.
    
    

    Horst

    #ifdef _AFXDLL
    #define BEGIN_MESSAGE_MAP_T(tparam, theClass, baseClass) \
    template tparam \
    const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
    { return &baseClass::messageMap; } \
    template tparam \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
    { return &theClass::messageMap; } \
    template tparam \
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
    { &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \
    template tparam \
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
    { \

    #else
    #define BEGIN_MESSAGE_MAP_T(tparam, theClass, baseClass) \
    template tparam \
    const AFX_MSGMAP* theClass::GetMessageMap() const \
    { return &theClass::messageMap; } \
    template tparam \
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
    { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
    template tparam \
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
    { \

    #endif

    /* Example:
    template <class BASE> class TWnd : public BASE
    {
    public:
    TWnd();
    protected:
    afx_msg void OnPaint();
    DECLARE_MESSAGE_MAP()
    };

    template <class BASE> TWnd<BASE>::TWnd() {
    }

    template <class BASE> void TWnd<BASE>::OnPaint() {
    ...
    }

    BEGIN_MESSAGE_MAP_T(<class BASE>, TWnd<BASE>, BASE)
    ON_WM_PAINT()
    END_MESSAGE_MAP()

    class CMyWnd : public TWnd<CStatic>
    {
    public:
    CMyWnd() {
    }
    };
    */

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Webinar on September 23, 2014, 2 p.m. ET / 11 a.m. PT Mobile commerce presents an array of opportunities for any business -- from connecting with your customers through mobile apps to enriching operations with mobile enterprise solutions. Join guest speaker, Michael Facemire, Forrester Research, Inc. Principal Analyst, as he discusses the new demands of mobile engagement and how application program interfaces (APIs) play a crucial role. Check out this upcoming webinar to learn about the new set of …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds