MFC使用C++的多继承

下来我们来看看如何在MFC中使用多继承(MI)。用一个东西总要知道为什么要用它。MIC++和面向对象编程界可是很有争议,MI可以新类从多个父类中继承一些属性。虽然存在争议,VC++的编译器和开发环境完全支持MIMFC类库设计得使用户不用理解什么是MI就可以使用了。我们也发现不需要使用MI编写类库,使用它编写某些应用程序也是可用可不用的,那么用与不同就看用户的喜好了。

如果你决定使用MI,那就看看如何使用它吧,下面有一些规定是基于C++的要求的,有一些是MFC专门要求的。

我们有必要看一下CruntimeClassMFC的永久和动态对象创建机器都使用它的数据结构来唯一标记一个类。在应用程序启动时,使用AFX_CLASSINIT的静态对象初始化一个应用程序。对于它的实现,用户不用操心。在VC++ 5中不支持MI运行(runtime)类型信息,但这并不意味着用户不能在MFC应用程序中使用MI

CObject::IsKindOf函数对于使用MI的类来说就不能正确识别它的类型了,原因很简单,它父类有多个,这个函数无法确定了。因此用户不能使用Cobject作为虚基类,所有对Cobject成员函数的调用都需要知道它的作用域,而且在调用的时候要特别小心。如果用户确实需要在MFC中使用MI,一定要保证在使用MI时,如果使用到了Cobject,一定要把它放在继承类的最左边。

下来让我们看看Cobject,这个所有类的老祖宗。所有的类都直接或间接继承来自Cobject,它没有数据成员,但有一些成员函数。在使用MI时,这些特定的成员函数会从Cobject的不同派生类中继承过来。

class CListWnd : public CFrameWnd, public CObList

{
...
};
CListWnd myListWnd;

上面的情况中,Cobject被包括了两次,这导致了两个问题:

  1. Cobject成员函数的调用必须是无二义的。
    myListWnd.Dump(afxDump); //这里会有一个编译错误,编译器不明白你调用的究竟是哪一个类的Dump,是CFrameWnd::Dump还是CObList::Dump
  2. 对静态成员函数包括'operator new''operator delete'的调用必须是无二义的。

有了问题我们就要解决,让我们看看如何解决:

在上面的情况中,你必须重新实现那些有二义性的函数,让我们来看个例子:

class CListWnd : public CFrameWnd, public CObList
{
public:
void* operator new(size_t nSize)   { return CFrameWnd::operator new(nSize); } //调用的是CframeWnd中的new

void operator delete(void* p)  { CFrameWnd::operator delete(p); } //调用的是CframeWnd中的delete

void Dump(CDumpContent& dc)  { CFrameWnd::Dump(dc);

CObList::Dump(dc); }
...
};

如果我们使用虚继承这些二义性会随之消失吗?不是这样。因为CObject中没有数据成员,所以也没有必要防止产生多个重复的基类数据成员复本。虚继承不能消除二义性,我们可以回想一下Dump函数,它仍然是二义的,因为CFrameWndCObject对它的实现是不同的。让我们看一下下面的步骤,它可以帮助你消除这种二义性:

运行(runtime)类型机制支持一些宏:DECLARE_DYNAMICIMPLEMENT_DYNAMICDECLARE_DYNCREATEIMPLEMENT_DYNCREATEDECLARE_SERIALIMPLEMENT_SERIAL,这些宏在单继承的时候工作得不错,可是对于多继承,出现在IMPLEMENT_DYNAMICIMPLEMENT_SERIAL中的必须是在继承关系最左边的基类。

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

对于消息映射可又有些麻烦了,它要求下面两项要求:

  1. 基类中只能有一个Wnd的导出类;

  2. 这个导出类必须在继承关系的最左边,在上类中CFrameWnd就在最左边。

下面一些例子是错误的:

class CTwoWindows : public CFrameWnd, public CEdit
{ ... }; // 两个CWnd复本

class CListEdit : public CObList, public CEdit
{ ... }; // Cedit(由CWnd导出)必须在最左边

下面我们看一下微软公司提供的类子,没事不要使用这种结构。

#include <afxwin.h>
class CHelloAppAndFrame : public CFrameWnd, public CWinApp
{ 
public:
    CHelloAppAndFrame()
        { }
    // Necessary evil for 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()
// since 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;