Delen via


TN016: Meervoudige overerving in C++ gebruiken met MFC

In deze notitie wordt beschreven hoe u meerdere overervingen (MI) gebruikt met de Microsoft Foundation Classes. Het gebruik van MI is niet vereist met MFC. MI wordt niet gebruikt in MFC-klassen en is niet vereist om een klassebibliotheek te schrijven.

In de volgende subonderwerpen wordt beschreven hoe MI van invloed is op het gebruik van veelvoorkomende MFC-idiomen, evenals het dekken van enkele van de beperkingen van MI. Sommige van deze beperkingen zijn algemene C++-beperkingen. Andere worden opgelegd door de MFC-architectuur.

Aan het einde van deze technische opmerking vindt u een volledige MFC-toepassing die gebruikmaakt van MI.

CRuntimeClass

De persistentie- en dynamische mechanismen voor het maken van objecten van MFC gebruiken de gegevensstructuur CRuntimeClass om klassen uniek te identificeren. MFC koppelt een van deze structuren aan elke dynamische en/of serialiseerbare klasse in uw toepassing. Deze structuren worden geïnitialiseerd wanneer de toepassing wordt gestart met behulp van een speciaal statisch object van het type AFX_CLASSINIT.

De huidige implementatie van CRuntimeClass biedt geen ondersteuning voor MI-runtime type-informatie. Dit betekent niet dat u MI niet kunt gebruiken in uw MFC-toepassing. U hebt echter bepaalde verantwoordelijkheden wanneer u werkt met objecten met meer dan één basisklasse.

De methode CObject::IsKindOf bepaalt niet correct het type van een object als deze meerdere basisklassen heeft. Daarom kunt u CObject niet gebruiken als een virtuele basisklasse, en alle aanroepen naar CObject lidfuncties zoals CObject::Serialize en CObject::operator nieuw moeten bereikkwalificaties hebben, zodat C++ de juiste functie-aanroep kan ondubbelzinnig maken. Wanneer een programma MI in MFC gebruikt, moet de klasse die de CObject basisklasse bevat de meest linkse klasse zijn in de lijst met basisklassen.

Een alternatief is om de dynamic_cast operator te gebruiken. Als u een object met MI castt naar een van de basisklassen, dwingt u de compiler om de functies in de opgegeven basisklasse te gebruiken. Zie dynamic_cast Operator voor meer informatie.

CObject - De wortel van alle klassen

Alle significante klassen zijn rechtstreeks of indirect afgeleid van klasse CObject. CObject heeft geen lidgegevens, maar heeft wel een aantal standaardfunctionaliteit. Wanneer u MI gebruikt, neemt u doorgaans over van twee of meer CObjectafgeleide klassen. In het volgende voorbeeld ziet u hoe een klasse kan overnemen van een CFrameWnd en een CObList:

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

In dit geval CObject wordt twee keer opgenomen. Dit betekent dat u een manier nodig hebt om verwijzingen naar CObject methoden of operators te onderscheiden. De operator nieuw en operator verwijderen zijn twee operators die ondubbelzinnig moeten zijn. In een ander voorbeeld veroorzaakt de volgende code een fout tijdens het compileren:

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

CObject-methoden opnieuw implementeren

Wanneer u een nieuwe klasse maakt met twee of meer CObject afgeleide basisklassen, moet u de CObject methoden die andere personen moeten gebruiken, opnieuw gebruiken. Operators new en delete zijn verplicht en Dump wordt aanbevolen. In het volgende voorbeeld worden de new operators en delete de Dump methode opnieuw geïmplementeerd:

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

Virtuele overname van CObject

Het lijkt erop dat virtueel overnemen CObject het probleem van dubbelzinnigheid van functies zou oplossen, maar dat is niet het geval. Omdat er geen ledengegevens in CObject zijn, hebt u geen virtuele overerving nodig om te voorkomen dat gegevens van een basisklasse-lid meermaals worden gekopieerd. In het eerste voorbeeld dat eerder werd weergegeven, is de Dump virtuele methode nog steeds dubbelzinnig omdat deze anders wordt geïmplementeerd in CFrameWnd en in CObList. De beste manier om dubbelzinnigheid te verwijderen, is door de aanbevelingen uit de vorige sectie te volgen.

CObject::IsKindOf en Run-Time Typen

Het runtime-typemechanisme dat door MFC wordt ondersteund, CObject maakt gebruik van de macro's DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL en IMPLEMENT_SERIAL. Deze macro's kunnen een runtime-typecontrole uitvoeren om veilige downcasts te garanderen.

Deze macro's ondersteunen slechts één basisklasse en werken op een beperkte manier voor het vermenigvuldigen van overgenomen klassen. De basisklasse die u opgeeft in IMPLEMENT_DYNAMIC of IMPLEMENT_SERIAL moet de eerste (of meest linkse) basisklasse zijn. Met deze plaatsing kunt u alleen typecontrole uitvoeren op de uiterst linkse basisklasse. Het runtime-typesysteem weet niets over aanvullende basisklassen. In het volgende voorbeeld zullen de runtimesystemen typecontrole uitvoeren op CFrameWnd, maar weten niets over CObList.

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

CWnd en berichtenkaarten

Voor een juiste werking van het MFC-berichttoewijzingssysteem zijn er twee aanvullende vereisten:

  • Er mag slechts één CWnd-afgeleide basisklasse zijn.

  • De CWnd-afgeleide basisklasse moet de eerste (of meest linkse) basisklasse zijn.

Hier volgen enkele voorbeelden die niet werken:

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

Een voorbeeldprogramma met MI

Het volgende voorbeeld is een zelfstandige toepassing die bestaat uit één klasse die is afgeleid van CFrameWnd en CWinApp. We raden u niet aan een toepassing op deze manier te structuren, maar dit is een voorbeeld van de kleinste MFC-toepassing met één 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;

Zie ook

Technische notities per nummer
Technische Aantekeningen Per Categorie