TN016: Using C++ Multiple Inheritance with MFC

This note describes how to use Multiple Inheritance (MI) with the Microsoft Foundation Classes.

Why Multiple Inheritance?

There is an ongoing debate in the C++ and object-oriented communities over the value of MI. The Visual C++ compiler and development environment fully supports MI.

The MFC class library has been designed so that you do not need to understand MI to use MFC. MI is not used in any of the MFC classes. We have found that MI is not required to write a class library, nor is it required for writing serious applications. To use MI or not can be a personal decision, so we leave that decision to you.

So You Want to Use MI?

If you already understand how to use MI, understand the performance trade-offs, and want to use MFC, this technote will tell you what you must do. Some of the restrictions are general C++ restrictions, others are imposed by the MFC architecture.

The following describes some of the technical issues of how MI affect the use of common MFC idioms. At the end of this technical note a complete MFC application using MI is included which you can extract and compile.

CRuntimeClass

The persistence and dynamic object creation mechanisms of MFC use the CRuntimeClass data structure to uniquely identify classes. MFC associates one structure of this type with each dynamic and/or serializable class in the application. These structures are initialized at application startup time using a special static object of type AFX_CLASSINIT. You need not concern yourself with the implementation of this information, as it is likely to change between revisions of MFC.

The current implementation of CRuntimeClass does not support Multiple Inheritance runtime type information. This does not mean you cannot use MI in your MFC application, but if you do, you will have certain responsibilities when working with objects that have more than one base class.

The CObject::IsKindOf member function will not correctly determine the type of an object if it has multiple base classes. Therefore, you cannot use CObject as a virtual base class, and all calls to CObject member functions such as Serialize and operator new will need to have scope qualifiers so that C++ can disambiguate the appropriate function call. If you do find the need to use MI within MFC, then you should be sure to make the class containing the CObject base class the left-most class in the list of base classes.

For advice on the uses and abuses of MI, see Advanced C++ Programming Styles and Idioms by James O. Coplien (Addison Wesley, 1992).

CObject - The Root of all Classes

As you know, all significant classes derive directly or indirectly from class CObject. CObject does not have any member data, but does have some default functionality. When using MI, it will be common to inherit from two or more CObject-derived classes, for example, a CFrameWnd and a CObList:

class CListWnd : public CFrameWnd, public CObList
{
 ...
};
CListWnd myListWnd;

In this case CObject is included twice, which leads to two problems:

Recommended Steps

When creating a new class with two or more CObject derived base classes, reimplement those CObject member that you expect people to use. Operators new and delete are mandatory, Dump is recommended. For example:

class CListWnd : public CFrameWnd, public CObList
{
public:
    void* operator new(size_t nSize)
        { return CFrameWnd::operator new(nSize); }
    void operator delete(void* p)
        { CFrameWnd::operator delete(p); }

    void Dump(CDumpContent& dc)
        { CFrameWnd::Dump(dc);
          CObList::Dump(dc); }
     ...
};

Virtual Inheritance of CObject ?

You may ask, "If you inherit CObject virtually, won't all of the ambiguity problems go away?".

Even in the efficient Microsoft Object Model, virtual inheritance is not as efficient as non-virtual inheritance (just as Multiple Inheritance is not as efficient as single inheritance in certain cases). Since there is no member data in CObject, virtual inheritance is not needed to prevent multiple copies of a base class's member data.

The real answer is no, virtual inheritance will not solve the ambiguity problems illustrated above. For example: the Dump virtual member function is still ambiguous (since CFrameWnd and CObList implement it differently).

Therefore we recommend following the steps above to provide disambiguation:

CObject::IsKindOf and Run-time Typing

The runtime typing mechanism supported by MFC in CObject uses the macros DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL and IMPLEMENT_SERIAL. These give the ability to do a run-time type check to allow for safe cast-downs.

These macros only support a single base class and will work in a limited way for multiply inherited classes. The base class you specify in IMPLEMENT_DYNAMIC or IMPLEMENT_SERIAL should be the first (or left-most) base class. For example,

class CListWnd : public CFrameWnd, public CObList
{
    DECLARE_DYNAMIC(CListWnd)
    ...
};
IMPLEMENT_DYNAMIC(CListWnd, CFrameWnd)

This will allow you to do type checking for the left-most base class only. The run-time type system will know nothing about additional bases (CObList in this case).

CWnd and Message Maps

In order for the MFC message map system to work correctly, there are two additional requirements:

In the example above, CFrameWnd is the first base class.

Some examples that will not work:

class CTwoWindows : public CFrameWnd, public CEdit
    { ... };
        // error : two copies of CWnd

class CListEdit : public CObList, public CEdit
    { ... };
        // error : CEdit (derived from CWnd) must be first

A Sample Program using MI

The following sample is a stand-alone application that consists of one class derived from CFrameWnd and CWinApp. This way of structuring an application is not a recommended, but this is an example of the smallest MFC application with one class.

You can cut the following program and copy it on top of HELLOAPP.CPP in the single-inheritance MFC General sample HELLOAPP. Then build the program as you would normally.

#include <afxwin.h>

class CHelloAppAndFrame : public CFrameWnd, public CWinApp
{ 
public:
    CHelloAppAndFrame()
        { }

    // Necessary evil for MI disambiguity
    void* operator new(size_t nSize)
        { return CFrameWnd::operator new(nSize); }
    void operator delete(void* p)
        { CFrameWnd::operator delete(p); }

// Implementation
    // CWinApp overrides
    virtual BOOL InitInstance();
    // CFrameWnd overrides
    virtual void PostNcDestroy();
    afx_msg void OnPaint();

    DECLARE_MESSAGE_MAP()

};

BEGIN_MESSAGE_MAP(CHelloAppAndFrame, CFrameWnd)
    ON_WM_PAINT()
END_MESSAGE_MAP()

// since the frame window is not allocated on the heap, we must
// override PostNCDestroy not to delete the frame object
void CHelloAppAndFrame::PostNcDestroy()
{
    // do nothing (do not call base class)
}

void CHelloAppAndFrame::OnPaint()
{
    CPaintDC dc(this);
    CRect rect;
    GetClientRect(rect);

    CString s = "Hello, Windows!";
    dc.SetTextAlign(TA_BASELINE | TA_CENTER);
    dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));
    dc.SetBkMode(TRANSPARENT);
    dc.TextOut(rect.right / 2, rect.bottom / 2, s);
}

// Application initialization
BOOL CHelloAppAndFrame::InitInstance()
{
    // first create the main frame
    if (!CFrameWnd::Create(NULL, "Multiple Inheritance Sample",
        WS_OVERLAPPEDWINDOW, rectDefault))
        return FALSE;

    // the application object is also a frame window
    m_pMainWnd = this;          
    ShowWindow(m_nCmdShow);
    return TRUE;
}

CHelloAppAndFrame theHelloAppAndFrame;

Technical Notes by NumberTechnical Notes by Category