Программирование на Visual C++. Архив рассылки
Шрифт:
Идентификация типов времени выполнения в MFC базируется на виртуальной функции GetRuntimeClass. Информация о типе доступна для любого объекта — потомка CObject, который включает в себя макросы DECLARE_DYNAMIC, DECLARE_DYNCREATE, или DECLARE_SERIAL. Эта информация позволяет Вам определить, может ли объект быть приведен к типу унаследованного класса или принадлежат ли два объекта одному и тому же классу. Хотя Visual C++ и не поддерживает новый C++-оператор dynamic_cast [помним, что речь
Информация о типе времени выполнения объявляется при помощи статической переменной класса, в данном случае 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 выделяет
Хотя в исходных текстах не дается пояснений, ясно, что такая схема четко разделяет конструирование объекта и выделение памяти. Все это кажется лишним, но в определенных ситуациях без такой организации не обойтись. Например, чтобы при создании массива его элементы располагались в одном блоке памяти, эту память нужно выделять одним вызовом функции malloc. Используя ConstructObject, Вы можете вручную инициализировать каждый элемент. Такой механизм позволяет принимать на этапе выполнения решения, которые в C++ обычно принимаются на этапе компиляции.
В примере 2 показана функция Construct. Синтаксис вызова new немного необычен. На самом деле вызывается функция CObject::operator new(size_t, void*). Помните, что размер структуры — это подразумеваемый аргумент при вызове new, однако его следует явно описать в определении оператора. Эта версия new в CObject ничего не делает, но вызов new дает побочным эффектом вызов конструктора для этого объекта. Память уже была выделена вызовом CreateObject с использованием информации о размере из CRuntimeClass.
Пример 2: Функция Construct.
Используя реестр классов CRuntimeClass и функцию-член Construct, MFC удается находить и создавать объекты новых типов на лету, что решает Проблему 1. Потенциально серьезные последствия данной техники в том, что при этом не поддерживаются множественное наследование и виртуальные базовые классы (см. MFC Technical Note #16).
Проблема 2 состоит в том, что пользователи должны иметь возможность легко добавлять новые классы в реестр. Идея саморегистрирующихся типов – ключевая идея объектно-ориентированного проектирования. Если каждый тип сам регистрирует факт своего существования в реестре, вместо того, чтобы программист прописывал его в реестре заранее, тогда типы можно свободно добавлять и удалять из программы, не меняя структуры реестра.
Хоть это и неочевидно, именно макрос IMPLEMENT_DYNCREATE позволяет пользователям без проблем добавлять новые классы в реестр. После развертывания IMPLEMENT_DYNCREATE, как показано на листинге 2, статическая структура CRuntimeClass в CScribDoc инициализируется так, как показано в примере 3.