Udostępnij za pośrednictwem


TN016: używanie dziedziczenia wielokrotnego języka C++ z MFC

W tej notatce opisano sposób używania wielu dziedziczenia (MI) z klasami programu Microsoft Foundation. Korzystanie z wystąpienia zarządzanego 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 podtopach opisano, w jaki sposób wystąpienie zarządzane wpływa na użycie typowych idiomów MFC, a także obejmuje 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 uwagi technicznej znajdziesz kompletną aplikację MFC korzystającą z mi.

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 programu nie obsługuje informacji o typie środowiska uruchomieniowego wystąpienia zarządzanego CRuntimeClass . Nie oznacza to, że nie można używać wystąpienia zarządzanego 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 wystąpienia zarządzanego w MFC, klasa zawierająca CObject klasę bazową musi być najbardziej lewą klasą na liście klas bazowych.

Alternatywą jest użycie dynamic_cast operatora . Rzutowanie obiektu za pomocą wystąpienia zarządzanego do jednej z jego klas bazowych wymusi, aby kompilator używał 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. W przypadku korzystania z wystąpienia zarządzanego zwykle dziedziczy się z co najmniej dwóch CObjectklas 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. Zalecane są operatory new i delete są obowiązkowe, a zrzut jest zalecany. Poniższy przykład ponownie zaimplementuje new operatory i i delete 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 programie nie ma żadnych danych CObjectskładowych, nie trzeba dziedziczenia wirtualnego, aby zapobiec wielu kopiom danych składowych klasy bazowej. W pierwszym przykładzie pokazanym wcześniej metoda wirtualna jest nadal niejednoznaczna, Dump ponieważ jest ona implementowana inaczej w systemach CFrameWnd i CObList. Najlepszym sposobem usunięcia niejednoznaczności jest przestrzeganie zaleceń przedstawionych w poprzedniej sekcji.

CObject::IsKindOf i wpisywanie w czasie wykonywania

Mechanizm wpisywania w czasie wykonywania 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 czasu wykonywania, aby zagwarantować bezpieczne obniżanie emisji.

Te makra obsługują tylko jedną klasę bazową i będą działać w ograniczony sposób dla klas dziedziczonej pomnożonej. Klasa bazowa określona w IMPLEMENT_DYNAMIC lub IMPLEMENT_SERIAL powinna być pierwszą (lub najbardziej lewą) klasą bazową. To umieszczenie umożliwi sprawdzanie typów tylko dla lewej klasy bazowej. System typu czasu wykonywania 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 CFrameWndelementu , ale nic o tym nie wiedzą.CObList

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

Mapy CWnd i Message

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

  • Musi istnieć tylko jedna CWndklasa bazowa pochodna.

  • 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 wystąpienia zarządzanego

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 też

Uwagi techniczne według numerów
Uwagi techniczne według kategorii