TN016: utilizzo dell'ereditarietà multipla C++ con MFC
Questa nota descrive come usare più ereditarietà (MI) con le classi Microsoft Foundation. L'uso dell'istanza gestita non è necessario con MFC. L'istanza gestita non viene usata in alcuna classe MFC e non è necessaria per scrivere una libreria di classi.
Gli argomenti secondari seguenti descrivono in che modo l'istanza gestita influisce sull'uso di idiomi MFC comuni, oltre a coprire alcune delle restrizioni dell'istanza gestita. Alcune di queste restrizioni sono restrizioni generali di C++. Altri sono imposti dall'architettura MFC.
Alla fine di questa nota tecnica è disponibile un'applicazione MFC completa che usa l'istanza gestita.
Cruntimeclass
I meccanismi di persistenza e creazione dinamica di oggetti di MFC usano la struttura di dati CRuntimeClass per identificare in modo univoco le classi. MFC associa una di queste strutture a ogni classe dinamica e/o serializzabile nell'applicazione. Queste strutture vengono inizializzate all'avvio dell'applicazione usando un oggetto statico speciale di tipo AFX_CLASSINIT
.
L'implementazione corrente di non supporta le informazioni sul tipo di runtime dell'istanza gestita CRuntimeClass
. Ciò non significa che non è possibile usare l'istanza gestita nell'applicazione MFC. Tuttavia, si avranno determinate responsabilità quando si lavora con oggetti con più classi di base.
Il metodo CObject::IsKindOf non determinerà correttamente il tipo di un oggetto se dispone di più classi di base. Pertanto, non è possibile usare CObject come classe base virtuale e tutte le chiamate a CObject
funzioni membro, ad esempio CObject::Serialize e CObject::operator new , devono avere qualificatori di ambito in modo che C++ possa disambiguare la chiamata di funzione appropriata. Quando un programma usa MI all'interno di MFC, la classe che contiene la CObject
classe di base deve essere la classe più a sinistra nell'elenco delle classi di base.
Un'alternativa consiste nell'usare l'operatore dynamic_cast
. Il cast di un oggetto con MI a una delle relative classi di base forza il compilatore a usare le funzioni nella classe base fornita. Per altre informazioni, vedere Operatore dynamic_cast.
CObject - Radice di tutte le classi
Tutte le classi significative derivano direttamente o indirettamente dalla classe CObject
. CObject
non dispone di dati dei membri, ma dispone di alcune funzionalità predefinite. Quando si usa l'istanza gestita, in genere si erediterà da due o più CObject
classi derivate. L'esempio seguente illustra come una classe può ereditare da un oggetto CFrameWnd e un oggetto CObList:
class CListWnd : public CFrameWnd, public CObList
{
// ...
};
CListWnd myListWnd;
In questo caso CObject
è incluso due volte. Ciò significa che è necessario un modo per disambiguare qualsiasi riferimento a CObject
metodi o operatori. L'operatore new e operator delete sono due operatori che devono essere disambiguati. Come altro esempio, il codice seguente causa un errore in fase di compilazione:
myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump
Metodi CObject reimplementing
Quando si crea una nuova classe con due o più CObject
classi di base derivate, è necessario rimplificare i CObject
metodi che si desidera che altri utenti usino. Gli operatori new
e delete
sono obbligatori e dump sono consigliati. Nell'esempio seguente viene riimposto gli new
operatori e delete
e il Dump
metodo :
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);
}
// ...
};
Ereditarietà virtuale di CObject
Potrebbe sembrare che l'ereditarietà CObject
virtualmente risolverebbe il problema dell'ambiguità della funzione, ma non è questo il caso. Poiché non sono presenti dati membro in CObject
, non è necessaria l'ereditarietà virtuale per impedire più copie di dati dei membri di una classe di base. Nel primo esempio illustrato in precedenza, il Dump
metodo virtuale è ancora ambiguo perché viene implementato in modo diverso in CFrameWnd
e CObList
. Il modo migliore per rimuovere l'ambiguità consiste nel seguire le raccomandazioni presentate nella sezione precedente.
CObject::IsKindOf e digitazione in fase di esecuzione
Il meccanismo di digitazione in fase di esecuzione supportato da MFC in CObject
usa le macro DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_edizione Standard RIAL e IMPLEMENT_edizione Standard RIAL. Queste macro possono eseguire un controllo del tipo di runtime per garantire downcast sicuri.
Queste macro supportano solo una singola classe di base e funzioneranno in modo limitato per moltiplicare le classi ereditate. La classe base specificata in IMPLEMENT_DYNAMIC o IMPLEMENT_edizione Standard RIAL deve essere la prima classe di base (o più a sinistra). Questo posizionamento consentirà di eseguire il controllo dei tipi solo per la classe di base più a sinistra. Il sistema dei tipi di runtime non conoscerà nulla sulle classi di base aggiuntive. Nell'esempio seguente, i sistemi di runtime eseguiranno il controllo dei tipi su CFrameWnd
, ma non sapranno nulla su CObList
.
class CListWnd : public CFrameWnd, public CObList
{
DECLARE_DYNAMIC(CListWnd)
// ...
};
IMPLEMENT_DYNAMIC(CListWnd, CFrameWnd)
Mappe CWnd e Message
Per il corretto funzionamento del sistema di mapping dei messaggi MFC, sono previsti due requisiti aggiuntivi:
Deve essere presente una
CWnd
sola classe di base derivata da .La
CWnd
classe di base derivata da deve essere la prima classe di base (o più a sinistra).
Ecco alcuni esempi che non funzioneranno:
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
Un programma di esempio che usa l'istanza gestita
L'esempio seguente è un'applicazione autonoma costituita da una classe derivata da CFrameWnd
e CWinApp. Non è consigliabile strutturare un'applicazione in questo modo, ma si tratta di un esempio dell'applicazione MFC più piccola con una classe.
#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;