TN016:搭配使用 C++多重繼承與 MFC
此提示說明如何對於 Microsoft Foundation Classes 搭配使用多重繼承 (MI)。 使用 MI 不需要 MFC。 MI 未使用於任何 MFC 類別,並且不需要撰寫類別庫。
以下副標題說明 MI 如何影響一般 MFC 慣例的使用並且涵蓋一些 MI 的限制。 其中一些限制為一般 C++ 限制。 其他則是 MFC 結構加上的限制。
在本技術提示結尾,您可找到使用 MI 的完整 MFC 應用程式。
CRuntimeClass
MFC 的持續性和動態物件建立機制會使用 CRuntimeClass 資料結構來唯一識別類別。 MFC 會在應用程式中將這些結構之一與各個動態和/或可序列化類別關聯。 當應用程式透過使用 AFX_CLASSINIT
類別的特殊靜態物件啟動時,會將這些結構初始化。
目前的 CRuntimeClass
實作不支援 MI 執行階段類型資訊。 這並不表示您在 MFC 應用程式中無法使用 MI。 不過,您在使用具有一個以上的基底類別的物件時,您會有一些責任。
如果物件有多個基類,CObject::IsKindOf 方法將無法正確判斷物件的類型。 因此,您無法使用 CObject 做為虛擬基類,而且對 CObject::Serialize 和 CObject::operator new 等 成員函式的所有呼叫 CObject
都必須有範圍限定詞,讓 C++ 可以厘清適當的函式呼叫。 當程式在 MFC 中使用 MI 時,包含 CObject
基底類別的類別必須是在基底類別清單中最左邊的類別。
替代方案是使用 dynamic_cast
運算子。 使用 MI 將物件轉換為其基底類別之一,會強制編譯器使用所提供的基底類別中的函式。 如需詳細資訊,請參閱 dynamic_cast 運算子 。
CObject - 所有類別的根。
所有重要類別都是直接或間接從類別 CObject
衍生。 CObject
並沒有任何成員資料,不過它確實有一些預設功能。 當您使用 MI 時,通常會從兩個或多個 CObject
衍生類別繼承。 下列範例說明類別如何繼承自 CFrameWnd 和 CObList :
class CListWnd : public CFrameWnd, public CObList
{
// ...
};
CListWnd myListWnd;
在此情況下,會包含 CObject
兩次。 這表示您需要一個方法使 CObject
方法或運算子的所有參考意義清楚。 運算子 new 和 operator delete 是必須厘清的兩個運算子。 做為另一個範例,下列程式碼會在編譯期間造成錯誤:
myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump
重新實作 CObject 方法
當您建立具有兩個或多個 CObject
衍生的基底類別的新類別時,您應該實作要其他人使用的 CObject
方法。 運算子 new
和 delete
是必要專案, 建議使用傾印 。 下列範例會重新實作 new
和 delete
運算子和 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);
}
// ...
};
CObject 的虛擬繼承
看起來也許虛擬繼承 CObject
函式會解決函式模稜兩可的問題,不過實際並不是這樣。 因為 CObject
中沒有成員資料,所以您並不需要虛擬繼承來防止產生基底類別成員資料的多個複本。 在稍早顯示的第一個範例中,Dump
虛擬方法仍然模稜兩可,因為它在 CFrameWnd
和 CObList
中的實作不一樣。 去除模稜兩可的最佳方式是遵照上一節所提出的建議。
CObject::IsKindOf 和執行階段類型
中 CObject
MFC 支援的執行時間輸入機制會使用宏DECLARE_DYNAMIC、IMPLEMENT_DYNAMIC、DECLARE_DYNCREATE、IMPLEMENT_DYNCREATE、DECLARE_SERIAL和IMPLEMENT_SERIAL。 這些巨集可執行執行階段類型檢查,以保證安全向下轉型。
這些巨集只支援單一基底類別,並且會對多重繼承的類別以限定的方式執行。 您在 IMPLEMENT_DYNAMIC 或 IMPLEMENT_SERIAL 中指定的基類應該是第一個 (或最左邊的) 基類。 這個位置可讓您只針對最左邊的基底類別進行類型檢查。 執行階段類型系統對於其他基底類別毫無所知。 在下列範例中,執行階段系統會對 CFrameWnd
進行類型檢查,但是對於 CObList
毫無所知。
class CListWnd : public CFrameWnd, public CObList
{
DECLARE_DYNAMIC(CListWnd)
// ...
};
IMPLEMENT_DYNAMIC(CListWnd, CFrameWnd)
CWnd 和訊息對應
若要讓 MFC 訊息對應系統正確運作,有兩項額外需求:
只能有一個
CWnd
衍生的基底類別。CWnd
衍生的基底類別必須是第一個 (或最左邊的) 基底類別。
以下一些不會運作的範例:
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
使用 MI 的範例程式
下列範例是獨立應用程式,其中包含衍生自 CFrameWnd
和 CWinApp 的一個類別。 我們不建議您以此方式建構應用程式,不過這是具有一個類別的最小 MFC 應用程式的範例。
#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;