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