此说明介绍如何将多个继承(MI)与 Microsoft 基础类配合使用。 MFC 不需要使用 MI。 MI 不用于任何 MFC 类,不需要编写类库。
以下子主题介绍了 MI 如何影响常见 MFC 成语的使用,以及涵盖 MI 的一些限制。 其中一些限制是一般C++限制。 其他由 MFC 体系结构施加。
在此技术说明结束时,你将找到使用 MI 的完整 MFC 应用程序。
CRuntimeClass
MFC 的持久性和动态对象创建机制使用 CRuntimeClass 数据结构来唯一标识类。 MFC 将其中一个结构与应用程序中的每个动态和/或可序列化类相关联。 当应用程序使用类型 AFX_CLASSINIT
特殊的静态对象启动时,将初始化这些结构。
当前实现 CRuntimeClass
不支持 MI 运行时类型信息。 这并不意味着不能在 MFC 应用程序中使用 MI。 但是,使用具有多个基类的对象时,将承担某些责任。
如果对象具有多个基类, 则 CObject::IsKindOf 方法无法正确确定对象的类型。 因此,不能将 CObject 用作虚拟基类,并且对 CObject
成员函数(如 CObject::Serialize 和 CObject::operator new )的所有调用都必须具有范围限定符,以便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
虚拟方法仍然不明确,因为它在和CObList
中CFrameWnd
以不同的方式实现。 删除歧义的最佳方式是遵循上一部分提供的建议。
CObject::IsKindOf 和 Run-Time 键入
MFC CObject
支持的运行时键入机制使用宏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;