Freigeben über


TN016: Verwenden von C++-Mehrfachvererbung mit MFC

In diesem Hinweis wird beschrieben, wie Sie mehrere Vererbungen (MI) mit den Microsoft Foundation-Klassen verwenden. Die Verwendung von MI ist bei MFC nicht erforderlich. MI wird nicht in MFC-Klassen verwendet und ist nicht erforderlich, um eine Klassenbibliothek zu schreiben.

Die folgenden Unterthemen beschreiben, wie MI sich auf die Verwendung gängiger MFC-Idiome auswirkt und einige der Einschränkungen von MI abdeckt. Einige dieser Einschränkungen sind allgemeine C++-Einschränkungen. Andere werden von der MFC-Architektur auferlegt.

Am Ende dieser technischen Notiz finden Sie eine vollständige MFC-Anwendung, die MI verwendet.

Cruntimeclass

Die Persistenz- und dynamischen Objekterstellungsmechanismen von MFC verwenden die CRuntimeClass-Datenstruktur , um Klassen eindeutig zu identifizieren. MFC ordnet eine dieser Strukturen jeder dynamischen und/oder serialisierbaren Klasse in Ihrer Anwendung zu. Diese Strukturen werden initialisiert, wenn die Anwendung mit einem speziellen statischen Objekt vom Typ AFX_CLASSINITbeginnt.

Die aktuelle Implementierung unterstützt CRuntimeClass keine MI-Laufzeittypinformationen. Dies bedeutet nicht, dass Sie MI nicht in Ihrer MFC-Anwendung verwenden können. Sie haben jedoch bestimmte Zuständigkeiten, wenn Sie mit Objekten arbeiten, die mehr als eine Basisklasse aufweisen.

Die CObject::IsKindOf-Methode bestimmt nicht ordnungsgemäß den Typ eines Objekts, wenn es über mehrere Basisklassen verfügt. Daher können Sie CObject nicht als virtuelle Basisklasse verwenden, und alle Aufrufe an CObject Memberfunktionen wie CObject::Serialize und CObject::operator new müssen Bereichsqualifizierer aufweisen, damit C++ den entsprechenden Funktionsaufruf eindeutig machen kann. Wenn ein Programm MI in MFC verwendet, muss die Klasse, die die CObject Basisklasse enthält, die linksste Klasse in der Liste der Basisklassen sein.

Eine Alternative besteht darin, den dynamic_cast Operator zu verwenden. Das Umwandeln eines Objekts mit MI in eine seiner Basisklassen erzwingt die Verwendung der Funktionen in der bereitgestellten Basisklasse. Weitere Informationen finden Sie unter dynamic_cast Operator.

CObject – Der Stamm aller Klassen

Alle signifikanten Klassen werden direkt oder indirekt von der Klasse CObjectabgeleitet. CObject verfügt nicht über Memberdaten, sie verfügt jedoch über einige Standardfunktionen. Wenn Sie MI verwenden, erben Sie in der Regel von zwei oder mehr CObjectabgeleiteten Klassen. Das folgende Beispiel veranschaulicht, wie eine Klasse von einem CFrameWnd und einer CObList erben kann:

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

In diesem Fall CObject ist zweimal enthalten. Dies bedeutet, dass Sie eine Möglichkeit benötigen, jegliche Verweise auf CObject Methoden oder Operatoren zu disambiguieren. Der Operator "Neu " und "Operator löschen " sind zwei Operatoren, die mehrdeutig sein müssen. Als weiteres Beispiel verursacht der folgende Code zur Kompilierungszeit einen Fehler:

myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump

Reimplementing CObject-Methoden

Wenn Sie eine neue Klasse mit zwei oder mehr CObject abgeleiteten Basisklassen erstellen, sollten Sie die CObject Methoden erneut anwenden, die andere Personen verwenden sollen. Operatoren new und sind obligatorisch und delete Dump wird empfohlen. Im folgenden Beispiel werden die new Operatoren und delete operatoren und die Dump Methode neu gestaltet:

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);
    }
    // ...
};

Virtuelle Vererbung von CObject

Es mag so aussehen, dass die virtuelle Vererbung CObject das Problem der Funktionsdeutigkeit lösen würde, aber das ist nicht der Fall. Da keine Memberdaten vorhanden CObjectsind, benötigen Sie keine virtuelle Vererbung, um mehrere Kopien einer Basisklassenmememmdaten zu verhindern. Im ersten Beispiel, das zuvor gezeigt wurde, ist die Dump virtuelle Methode immer noch mehrdeutig, da sie anders CFrameWnd in und CObListimplementiert wird. Die beste Möglichkeit, Mehrdeutigkeit zu entfernen, besteht darin, die im vorherigen Abschnitt dargestellten Empfehlungen zu befolgen.

CObject::IsKindOf und Laufzeiteingabe

Der von MFC CObject unterstützte Laufzeiteingabemechanismus verwendet die Makros DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL und IMPLEMENT_SERIAL. Diese Makros können eine Laufzeittypüberprüfung durchführen, um sichere Downcasts zu gewährleisten.

Diese Makros unterstützen nur eine einzelne Basisklasse und funktionieren auf begrenzte Weise für multiplizierte geerbte Klassen. Die Basisklasse, die Sie in IMPLEMENT_DYNAMIC oder IMPLEMENT_SERIAL angeben, sollte die erste (oder linksste) Basisklasse sein. Mit dieser Platzierung können Sie die Typüberprüfung nur für die linksste Basisklasse durchführen. Das Laufzeittypsystem kennt nichts über zusätzliche Basisklassen. Im folgenden Beispiel führen die Laufzeitsysteme die Typüberprüfung durch CFrameWnd, wissen aber nichts über CObList.

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

CWnd- und Nachrichten-Karten

Damit das MFC-Nachrichtenzuordnungssystem ordnungsgemäß funktioniert, gibt es zwei zusätzliche Anforderungen:

  • Es darf nur eine CWndabgeleitete Basisklasse vorhanden sein.

  • Die CWndabgeleitete Basisklasse muss die erste (oder linksste) Basisklasse sein.

Hier sind einige Beispiele, die nicht funktionieren:

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

Beispielprogramm mit MI

Das folgende Beispiel ist eine eigenständige Anwendung, die aus einer klasse besteht, die von CFrameWnd und CWinApp abgeleitet ist. Es wird nicht empfohlen, eine Anwendung auf diese Weise zu strukturieren, aber dies ist ein Beispiel für die kleinste MFC-Anwendung mit einer Klasse.

#include <afxwin.h>

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

    // Necessary because of 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()

// because 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;

Siehe auch

Technische Hinweise – nach Nummern geordnet
Technische Hinweise – nach Kategorien geordnet