Идентификация типов времени выполнения в MFC базируется на виртуальной функции GetRuntimeClass() . Информация о типе доступна для любого объекта — потомка CObject , который включает в себя макросы DECLARE_DYNAMIC, DECLARE_DYNCREATE, или DECLARE_SERIAL. Эта информация позволяет Вам определить, может ли объект быть приведен к типу унаследованного класса или принадлежат ли два объекта одному и тому же классу. Хотя Visual C++ и не поддерживает новый C++-оператор dynamic_cast [ помним, что речь идет о Visual C++ 2.1 - прим.пер. ], использование вышеописанной информации о типе времени выполнения даст тот же эффект.
Информация о типе времени выполнения объявляется при помощи статической переменной класса, в данном случае classCScribDoc . Это имя (без пробела внутри) создается в макросах DECLARE_xxx через оператор макро-конкатенации. Для доступа к этой переменной используются функции класса _GetBaseClass() и GetRuntimeClass() . GetRuntimeClass() является виртуальной, поэтому тип объекта может быть определен даже через указатель на CObject .
И наконец, статическая функция класса Construct() образует базис для использования реестра классов MFC как фабрики классов, которая может, когда требуется, создавать объекты произвольных типов. Для понимания работы Construct() необходимы дополнительные разьяснения.
Создание объекта
В книге Advanced C++: Programming Styles and Idioms (Addison-Wesley, 1992), Джеймс O. Коплин так описывает концепцию виртуального конструктора:
Виртуальный конструктор используется в случае, когда необходимо определять тип объекта из контекста, в котором конструируется этот объект.
В MFC контекстом является информация, прочитанная из упорядоченного (serialized) архива [ под архивом в этой статье понимается хранилище данных – прим.пер. ]. Однако, виртуальный конструктор – это лишь концепция; никакая конструкция языка не реализует ее напрямую. Оператор new требует явного указания типа. Виртуальные конструкторы могут быть реализованы, если ввести в каждый класс статическую функцию, которая будет вызывать new для этого класса. Эта статическая функция-член будет вызываться при создании объекта определенного типа.
В MFC эта функция-член называется Construct() . Она создается макросами IMPLEMENT_DYNCREATE или IMPLEMENT_SERIAL. Один из этих макросов обязательно должен появиться в .cpp-модуле ровно один раз для каждого класса с поддержкой динамического создания. В случае со Scribble, выражение IMPLEMENT_DYNCREATE(CScribDoc, CDocument) появляется почти в самом начале файла SCRIBDOC.CPP. Первым аргументом идет класс, а вторым – его класс-родитель. Листинг 2 показывает код, сгенерированный препроцессором.
Когда MFC нужен документ или любой другой объект класса, унаследованного от CObject , она вызывает функцию CreateObject() класса CRuntimeClass. CreateObject() выделяет память, используя размер, указанный в структуре CRuntimeClass , и после этого вызывает ConstructObject(). ConstructObject() проверяет, поддерживает ли класс динамическое конструирование и вызывает функцию Construct() создаваемого класса.
Хотя в исходных текстах не дается пояснений, ясно, что такая схема четко разделяет конструирование объекта и выделение памяти. Все это кажется лишним, но в определенных ситуациях без такой организации не обойтись. Например, чтобы при создании массива его элементы располагались в одном блоке памяти, эту память нужно выделять одним вызовом функции malloc() . Используя ConstructObject() , Вы можете вручную инициализировать каждый элемент. Такой механизм позволяет принимать на этапе выполнения решения, которые в C++ обычно принимаются на этапе компиляции.
В примере 2 показана функция Construct() . Синтаксис вызова new немного необычен. На самом деле вызывается функция CObject::operator new(size_t, void*) . Помните, что размер структуры — это подразумеваемый аргумент при вызове new , однако его следует явно описать в определении оператора. Эта версия new в CObject ничего не делает, но вызов new дает побочным эффектом вызов конструктора для этого объекта. Память уже была выделена вызовом CreateObject с использованием информации о размере из CRuntimeClass .
Пример 2: Функция Construct().
void __stdcall CScribDoc::Construct(void* p) {
new(p) CScribDoc;
}
Используя реестр классов CRuntimeClass и функцию-член Construct() , MFC удается находить и создавать объекты новых типов на лету, что решает Проблему 1. Потенциально серьезные последствия данной техники в том, что при этом не поддерживаются множественное наследование и виртуальные базовые классы (см. MFC Technical Note #16).
Читать дальше