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ù CObjectclassi 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 CWndsola classe di base derivata da .

  • La CWndclasse 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;

Vedi anche

Note tecniche per numero
Note tecniche per categoria