Dela via


TN016: Använda C++ multipelt arv med MFC

Den här anteckningen beskriver hur du använder flera arv (MI) med Microsoft Foundation-klasserna. Användning av MI krävs inte med MFC. MI används inte i några MFC-klasser och krävs inte för att skriva ett klassbibliotek.

Följande subtopics beskriver hur MI påverkar användningen av vanliga MFC idiomer samt täcker några av begränsningarna för MI. Vissa av dessa begränsningar är allmänna C++-begränsningar. Andra införs av MFC-arkitekturen.

I slutet av den här tekniska anmärkningen hittar du ett fullständigt MFC-program som använder MI.

CRuntimeClass

Mekanismerna för beständighet och dynamiskt objektskapande i MFC använder datastrukturen CRuntimeClass för att unikt identifiera klasser. MFC associerar en av dessa strukturer med varje dynamisk och/eller serialiserbar klass i ditt program. Dessa strukturer initieras när programmet startar med hjälp av ett särskilt statiskt objekt av typen AFX_CLASSINIT.

Den aktuella implementeringen av CRuntimeClass stöder inte MI-körningstypinformationen. Det betyder inte att du inte kan använda MI i ditt MFC-program. Du har dock vissa ansvarsområden när du arbetar med objekt som har mer än en basklass.

Metoden CObject::IsKindOf avgör inte korrekt typen av ett objekt om det har flera basklasser. Därför kan du inte använda CObject som en virtuell basklass, och alla anrop till CObject medlemsfunktioner som CObject::Serialize och CObject::operator new måste ha omfångskvalificerare så att C++ kan skilja rätt funktionsanrop åt. När ett program använder MI i MFC måste klassen som innehåller basklassen CObject vara den vänstra klassen i listan över basklasser.

Ett alternativ är att använda operatorn dynamic_cast . Om du omvandlar ett objekt med MI till en av dess basklasser tvingar kompilatorn att använda funktionerna i den angivna basklassen. Mer information finns i dynamic_cast Operator.

CObject – Roten för alla klasser

Alla viktiga klasser härleds direkt eller indirekt från klassen CObject. CObject inte har några medlemsdata, men de har vissa standardfunktioner. När du använder MI ärver du vanligtvis från två eller flera CObject-härledda klasser. I följande exempel visas hur en klass kan ärva från en CFrameWnd och en CObList:

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

I det här fallet CObject ingår två gånger. Det innebär att du behöver ett sätt att skilja på alla referenser till CObject metoder eller operatorer. Operatorn new och operator delete är två operatorer som måste vara tvetydiga. Som ett annat exempel orsakar följande kod ett fel vid kompileringstillfället:

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

Omimplementering av CObject-metoder

När du skapar en ny klass med två eller flera CObject härledda baser bör du omimplementera de CObject metoder som du vill att andra ska kunna använda. Operatorer new och delete är obligatoriska och Dump rekommenderas. I följande exempel omimplementering av operatorerna new och delete och Dump -metoden:

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

Virtuellt arv av CObject

Det kan tyckas att ärva praktiskt taget CObject skulle lösa problemet med funktionsambiguitet, men det är inte fallet. Eftersom det inte finns några medlemsdata i CObjectbehöver du inte virtuellt arv för att förhindra flera kopior av en basklassmedlemsdata. I det första exemplet som visades tidigare är den Dump virtuella metoden fortfarande tvetydig eftersom den implementeras på olika sätt i CFrameWnd och CObList. Det bästa sättet att ta bort tvetydighet är att följa rekommendationerna i föregående avsnitt.

CObject::IsKindOf och Run-Time typning

Mekanismen för körningsskrivning som stöds av MFC i CObject använder makron DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL och IMPLEMENT_SERIAL. Dessa makron kan utföra en körningstypskontroll för att garantera säkra nedkast.

Dessa makron stöder endast en enskild basklass och fungerar på ett begränsat sätt för att multiplicera ärvda klasser. Basklassen som du anger i IMPLEMENT_DYNAMIC eller IMPLEMENT_SERIAL ska vara den första (eller mest vänstra) basklassen. Med den här placeringen kan du endast göra typkontroll för den vänstra basklassen. Körtidens typsystem känner inte till extra basklasser. I följande exempel gör körsystemen typkontroller mot CFrameWnd, men vet ingenting om CObList.

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

CWnd och Message Maps

För att MFC-meddelandekartsystemet ska fungera korrekt finns det två ytterligare krav:

  • Det får bara finnas en CWnd-härledd basklass.

  • Den CWnd-deriverade basklassen måste vara den första (eller vänstra) basklassen.

Här är några exempel som inte fungerar:

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

Ett exempelprogram med MI

Följande exempel är ett fristående program som består av en klass som härleds från CFrameWnd och CWinApp. Vi rekommenderar inte att du strukturerar ett program på det här sättet, men det här är ett exempel på det minsta MFC-programmet som har en klass.

#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;

Se även

tekniska anteckningar efter nummer
tekniska anteckningar efter kategori