Поделиться через


TN016. Использование множественного наследования C++ с MFC

В этом примечании описывается использование нескольких наследования (MI) с классами Microsoft Foundation. Использование MI не требуется для MFC. MI не используется в классах MFC и не требуется для записи библиотеки классов.

В следующих подтопиках описывается, как MI влияет на использование распространенных идиом MFC, а также охватывает некоторые ограничения MI. Некоторые из этих ограничений являются общими ограничениями C++. Другие применяются архитектурой MFC.

В конце этой технической заметки вы найдете полное приложение MFC, использующее MI.

Cruntimeclass

Механизмы сохраняемости и динамического создания объектов MFC используют структуру данных CRuntimeClass для уникального определения классов. MFC связывает одну из этих структур с каждым динамическим и/или сериализуемым классом в приложении. Эти структуры инициализированы при запуске приложения с помощью специального статического объекта типа AFX_CLASSINIT.

Текущая реализация не поддерживает сведения о типе CRuntimeClass среды выполнения MI. Это не означает, что вы не можете использовать MI в приложении MFC. Однако при работе с объектами, имеющими более одного базового класса, у вас будут определенные обязанности.

Метод CObject::IsKindOf неправильно определяет тип объекта, если он имеет несколько базовых классов. Поэтому нельзя использовать CObject в качестве виртуального базового класса, а все вызовы CObject функций-членов, таких как CObject::Serialize и CObject::operator new, должны иметь область квалификаторы, чтобы C++ может диффегировать соответствующий вызов функции. Если программа использует MI в MFC, класс, содержащий CObject базовый класс, должен быть левым в списке базовых классов.

Альтернативой является использование dynamic_cast оператора. Приведение объекта с помощью MI к одному из его базовых классов приведет компилятору к использованию функций в предоставленном базовом классе. Дополнительные сведения см. в разделе dynamic_cast Оператор.

CObject — корень всех классов

Все значимые классы являются производными напрямую или косвенно от класса CObject. CObject не содержит никаких данных-членов, но имеет некоторые функции по умолчанию. При использовании MI обычно наследуется от двух или более CObjectпроизводных классов. В следующем примере показано, как класс может наследоваться от CFrameWnd и CObList:

class CListWnd : public CFrameWnd, public CObList
{
    // ...
};
CListWnd myListWnd;

В этом случае CObject включается два раза. Это означает, что вам нужен способ диамбигуации любой ссылки на CObject методы или операторы. Оператор new and operator delete — это два оператора, которые должны быть диамбигуированы. В другом примере следующий код вызывает ошибку во время компиляции:

myListWnd.Dump(afxDump); // compile time error, CFrameWnd::Dump or CObList::Dump

Повторное выполнение методов CObject

При создании нового класса с двумя или более CObject производными базовыми классами следует повторно использовать CObject методы, которые нужно использовать другим пользователям. Операторы new и являются обязательными и deleteрекомендуется дампа . В следующем примере выполняется повторное выполнение new и операторы и deleteDump метод.

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 и ввод во время выполнения

Механизм ввода во время выполнения, поддерживаемый 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 и message Карты

Для правильной работы системы сопоставления сообщений 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

Следующий пример представляет собой автономное приложение, состоящее из одного класса, производного от CFrameWndCWinApp. Мы не рекомендуем структурировать приложение таким образом, но это пример наименьшего приложения 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;

См. также

Технические примечания по номеру
Технические примечания по категории