Udostępnij przez


TN016: Zastosowanie wielokrotnego dziedziczenia w C++ przy użyciu MFC

Ta notatka opisuje sposób używania wielodziedziczenia (MI) z klasami Microsoft Foundation. Użycie MI nie jest wymagane w przypadku MFC. Wystąpienie zarządzane nie jest używane w żadnych klasach MFC i nie jest wymagane do pisania biblioteki klas.

W poniższych podtematach opisano, w jaki sposób wielodziedziczenie wpływa na użycie typowych idiomów MFC, a także omawia niektóre ograniczenia MI. Niektóre z tych ograniczeń są ogólnymi ograniczeniami języka C++. Inne są nakładane przez architekturę MFC.

Na końcu tej noty technicznej znajdziesz kompletną aplikację MFC korzystającą z MI.

Klasa CRuntimeClass

Mechanizmy tworzenia obiektów trwałych i dynamicznych MFC używają struktury danych CRuntimeClass do unikatowego identyfikowania klas. MFC kojarzy jedną z tych struktur z każdą dynamiczną i/lub serializowalnymi klasami w aplikacji. Te struktury są inicjowane, gdy aplikacja rozpoczyna się przy użyciu specjalnego statycznego obiektu typu AFX_CLASSINIT.

Bieżąca implementacja CRuntimeClass nie obsługuje informacji o typie środowiska uruchomieniowego MI. Nie oznacza to, że nie można używać MI w aplikacji MFC. Jednak podczas pracy z obiektami, które mają więcej niż jedną klasę bazową, będziesz mieć pewne obowiązki.

Metoda CObject::IsKindOf nie określi poprawnie typu obiektu, jeśli ma wiele klas bazowych. W związku z tym nie można użyć obiektu CObject jako wirtualnej klasy bazowej, a wszystkie wywołania CObject funkcji składowych, takich jak CObject::Serialize i CObject::operator new , muszą mieć kwalifikatory zakresu, aby język C++ mógł uściślać odpowiednie wywołanie funkcji. Gdy program używa wielodziedziczenia w MFC, klasa zawierająca CObject klasę bazową musi być najbardziej po lewej klasą na liście klas bazowych.

Alternatywą jest użycie dynamic_cast operatora . Rzutowanie obiektu za pomocą wielokrotnego dziedziczenia do jednej z jego klas bazowych spowoduje, że kompilator będzie musiał użyć funkcji w podanej klasie bazowej. Aby uzyskać więcej informacji, zobacz operator dynamic_cast.

CObject — katalog główny wszystkich klas

Wszystkie znaczące klasy pochodzą bezpośrednio lub pośrednio z klasy CObject. CObject nie ma żadnych danych członkowskich, ale ma pewne funkcje domyślne. Podczas korzystania z dziedziczenia wielokrotnego, zwykle dziedziczy się po co najmniej dwóch CObjectklasach pochodnych. Poniższy przykład ilustruje, jak klasa może dziedziczyć z CFrameWnd i CObList:

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

W tym przypadku CObject są uwzględniane dwa razy. Oznacza to, że potrzebujesz sposobu uściślania wszelkich odwołań do CObject metod lub operatorów. Operator new i operator delete to dwa operatory, które muszą być uściślane. W innym przykładzie poniższy kod powoduje błąd w czasie kompilacji:

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

Ponowne wdrażanie metod CObject

Podczas tworzenia nowej klasy, która ma co najmniej dwie CObject pochodne klasy bazowe, należy ponownie zaimplementować CObject metody, które mają być używane przez inne osoby. Operatory new i delete są obowiązkowe, a dump jest zalecany. Poniższy przykład ponownie implementuje operatory new i delete oraz metodę Dump:

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

Dziedziczenie wirtualne obiektu CObject

Może się wydawać, że praktycznie dziedziczenie CObject rozwiąże problem niejednoznaczności funkcji, ale tak nie jest. Ponieważ w CObject nie ma danych składowych, nie potrzebujesz dziedziczenia wirtualnego, aby zapobiec wielokrotnym kopiowaniu danych składowej klasy bazowej. W pierwszym przykładzie pokazanym wcześniej metoda wirtualna Dump jest nadal niejednoznaczna, ponieważ jest implementowana inaczej w CFrameWnd i CObList. Najlepszym sposobem usunięcia niejednoznaczności jest przestrzeganie zaleceń przedstawionych w poprzedniej sekcji.

CObject::IsKindOf i Run-Time Typowanie

Mechanizm typowania w czasie rzeczywistym obsługiwany przez MFC CObject używa makr DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC, DECLARE_DYNCREATE, IMPLEMENT_DYNCREATE, DECLARE_SERIAL i IMPLEMENT_SERIAL. Te makra mogą przeprowadzać sprawdzanie typu w czasie wykonywania, aby zagwarantować bezpieczne rzutowanie w dół.

Te makra obsługują tylko jedną klasę bazową i będą działać w ograniczony sposób dla klas wielodziedziczonych. Klasa bazowa określona w IMPLEMENT_DYNAMIC lub IMPLEMENT_SERIAL powinna być pierwszą (lub najbardziej lewą) klasą bazową. To ustawienie umożliwi sprawdzanie typów tylko dla najbardziej lewej klasy bazowej. System typów czasu wykonania nie będzie wiedział nic o dodatkowych klasach bazowych. W poniższym przykładzie systemy czasu wykonywania będą wykonywać sprawdzanie typów względem CFrameWnd, ale nie będą nic wiedzieć o CObList.

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

CWnd i mapy wiadomości

Aby system mapy komunikatów MFC działał prawidłowo, istnieją dwa dodatkowe wymagania:

  • Musi istnieć tylko jedna klasa bazowa pochodna z CWnd.

  • Klasa bazowa pochodna CWndmusi być pierwszą (lub najbardziej lewą) klasą bazową.

Oto kilka przykładów, które nie będą działać:

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

Przykładowy program korzystający z MI

Poniższy przykład to autonomiczna aplikacja, która składa się z jednej klasy pochodzącej z CFrameWnd i CWinApp. Nie zalecamy tworzenia struktury aplikacji w ten sposób, ale jest to przykład najmniejszej aplikacji MFC, która ma jedną klasę.

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

Zobacz także

Uwagi techniczne według numeru
Uwagi techniczne według kategorii