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 CObject
odvozený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 CObject
neexistují žá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
CWnd
odvozená základní třída.-
CWnd
odvozená 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í