Sdílet prostřednictvím


TN016: Použití vícenásobné dědičnosti jazyka C++ v prostředí MFC

Tato poznámka popisuje, jak používat více dědičnosti (MI) s třídami Microsoft Foundation. Použití MI se v prostředí MFC nevyžaduje. Mi se nepoužívá v žádné třídě MFC a není nutné psát knihovnu tříd.

Následující dílčí témata popisují, jak MI ovlivňuje používání běžných idiomů MFC a pokrývá některá omezení MI. Některá z těchto omezení jsou obecná omezení jazyka C++. Jiné jsou vynucovány architekturou MFC.

Na konci této technické poznámky najdete kompletní aplikaci MFC, která používá MI.

CRuntimeClass

Mechanismy trvalosti a dynamického vytváření objektů MFC používají datovou strukturu CRuntimeClass k jednoznačné identifikaci tříd. MFC přidruží jednu z těchto struktur ke každé dynamické a/nebo serializovatelné třídě v aplikaci. Tyto struktury se inicializují při spuštění aplikace pomocí speciálního statického objektu typu AFX_CLASSINIT.

Aktuální implementace CRuntimeClass nepodporuje informace o typu modulu runtime MI. To neznamená, že v aplikaci MFC nemůžete použít mi. Pokud ale pracujete s objekty, které mají více než jednu základní třídu, budete mít určité odpovědnosti.

CObject ::IsKindOf metoda nebude správně určit typ objektu, pokud má více základních tříd. Proto nelze použít objekt CObject jako virtuální základní třídu a všechna volání CObject členských funkcí, jako je CObject::Serialize a CObject::operator new , musí mít kvalifikátory oboru, aby jazyk C++ mohl nejednoznačit příslušné volání funkce. Pokud program používá mi v prostředí MFC, třída, která obsahuje CObject základní třídu, musí být levou nejvyšší třídou v seznamu základních tříd.

Alternativou je použití operátoru dynamic_cast . Přetypování objektu pomocí MI na jednu z jeho základních tříd vynutí kompilátoru používat funkce v zadané základní třídě. Další informace naleznete v tématu dynamic_cast – operátor.

CObject – kořen všech tříd

Všechny významné třídy jsou odvozeny přímo nebo nepřímo z třídy CObject. CObject nemá žádná data členů, ale má některé výchozí funkce. Když použijete MI, obvykle dědíte ze dvou nebo více CObjectodvozených tříd. Následující příklad ukazuje, jak třída může dědit z CFrameWnd a CObList:

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

V tomto případě CObject se zahrne dvakrát. To znamená, že potřebujete způsob, jak nejednoznačit jakýkoli odkaz na CObject metody nebo operátory. Nový operátor a odstranění operátoru jsou dva operátory, které musí být nejednoznačné. V jiném příkladu následující kód způsobí chybu v době kompilace:

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

Reimplementing CObject – metody

Když vytvoříte novou třídu, která má dvě nebo více CObject odvozených základních tříd, měli byste znovu vytvořit CObject metody, které mají používat jiní uživatelé. Operátory new a delete jsou povinné a doporučuje se výpis stavu systému . Následující příklad reimplements the new and delete operators and the Dump method:

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

Virtuální dědičnost objektu CObject

Může se zdát, že prakticky zděděné CObject by vyřešilo problém nejednoznačnosti funkce, ale to není případ. Vzhledem k tomu, že CObjectneexistují žádná členová data, nepotřebujete virtuální dědičnost, abyste zabránili více kopiím dat členů základní třídy. V prvním příkladu, který byl zobrazen dříve, je virtuální metoda stále nejednoznačný, Dump protože je implementována odlišně v CFrameWnd a CObList. Nejlepší způsob, jak odebrat nejednoznačnost, je postupovat podle doporučení uvedených v předchozí části.

CObject::IsKindOf a typování za běhu

Mechanismus psaní za běhu podporovaný prostředím MFC CObject používá makra DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL a IMPLEMENT_SERIAL. Tato makra můžou provádět kontrolu typů za běhu, která zaručuje bezpečné downcasty.

Tato makra podporují pouze jednu základní třídu a budou fungovat omezeným způsobem pro násobené třídy. Základní třída, kterou zadáte v IMPLEMENT_DYNAMIC nebo IMPLEMENT_SERIAL, by měla být první (nebo úplně vlevo) základní třída. Toto umístění vám umožní provést kontrolu typů pouze u základní třídy vlevo. Systém typů za běhu nebude o dalších základních třídách nic vědět. V následujícím příkladu systémy za běhu budou provádět kontrolu typů proti CFrameWnd, ale nebudou vědět nic o CObList.

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

CWnd a Mapy zpráv

Aby systém mapování zpráv MFC fungoval správně, existují dva další požadavky:

  • Musí existovat pouze jedna CWndodvozená základní třída.

  • - CWndodvozená základní třída musí být první (nebo levá-nejvíce) základní třída.

Tady je několik příkladů, které nebudou fungovat:

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

Ukázkový program s využitím MI

Následující ukázka je samostatná aplikace, která se skládá z jedné třídy odvozené z CFrameWnd A CWinApp. Tímto způsobem nedoporučujeme strukturovat aplikaci, ale jedná se o příklad nejmenší aplikace MFC, která má jednu třídu.

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

Viz také

Technické poznámky podle čísel
Technické poznámky podle kategorií