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 CObject
klas 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 CObject
skł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 CFrameWnd
elementu , 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
CWnd
klasa bazowa pochodna.Klasa bazowa pochodna
CWnd
musi 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