![]() |
Модераторы: Daevaorn |
![]() ![]() ![]() |
|
chipset |
|
||||||||||||||||||||||||||||||||||||||||
Эксперт ![]() ![]() ![]() ![]() Профиль Группа: Экс. модератор Сообщений: 4067 Регистрация: 11.1.2003 Где: Seattle, US Репутация: 27 Всего: 164 |
Идея такова:
- если существует необходимость во время работы программы определить, какого класса объект, имея в наличии только указатель на него, или - если нужно создать объект такого же класса, не зная, какого именно класса, но имея в наличии указатель на "готовый" объект, или - если нужно загрузить из файла/потока массив объектов различных классов, не зная заранее, в какой последовательности они записаны, или - решать похожие задачи то нужно хранить внутри каждого объекта в каком-то виде информацию о классе, которому принадлежит объект (а также желательно там-же хранить информацию о базовом классе(-ах), и указатель на функцию-конструктор объектов данного класса). Для экономии памяти (чтобы не хранить внутри ста объектов одного и того же класса одинаковую информацию), можно использовать статические компонентные данные. Чтобы как-то "объединить" имя класса, указатель на функцию-конструктор, информацию о базовом классе, можно сделать специальный класс-хранитель инфы о классе (как CRuntimeClass в MFC). Для подобных целей Borland ввела технологию RTTI (run-time type identification). Со своей стороны, Microsoft начиная с четвертой версии MFC, отказалась от RTTI и ввела собственную технологию (связка "CObject+CRuntimeClass+макросы 3-х видов"). Итак, чем же отличается реализация Microsoft: - информация о классе, перечисленная выше, хранится в объекте CRuntimeClass. Для сравнения классов двух объектов, достаточно сравнить указатели на CRuntimeClass-ы, которые можно получить используя GetRuntimeClass либо макрос RUNTIME_CLASS:
- для создания объекта любого класса, используется метод CRuntimeClass::CreateObject. Например, создать объект того же класса, что и указанный объект, можно так:
- для получения имени класса:
- для определения, является ли класс A производным от класса B:
- для загрузки из архива (аналог потока в MFC) объекта неизвестного заранее класса:
И, наконец, требования к классам пользователя, поддерживающим такую технологию: - все объекты, поддерживающие такую технологию д.б. производны от CObject - в определение класса нужно включить один из макросов: DECLARE_DYNAMIC - если нужно только иметь в объекте CRuntimeClass; DECLARE_DYNCREATE - если хочется еще и создавать объекты с пом. CreateObject; DECLARE_SERIAL - если хочется еще и грузить объекты из архива, не зная класса. Все три макроса имеют только 1 аргумент - имя класса. - вне класса нужно добавить макрос IMPLEMENT_xxx, где "xxx" - соответственно "DYNAMIC", "DYNCREATE" или "SERIAL". Две первые версии макросов идут с 2-мя параметрами - имя класса и базового класса; третья имеет еще один параметр - номер версии объекта (нужно для поддержки "супер-пупер" работы с версиями объектов ![]() Пример:
Недостатки мелкософтовской системы: - у класса м.б. только 1 базовый класс (точнее, из может быть сколько угодно, но с пом. IsDerivedFrom можно определить только, производен ли класс от того, что был прописан в IMPLEMENT_xxx) - всё время приходится писать эти проклятые макросы ![]() В новой версии MFC (7.0) добавлено пару мелких фич, но система в целом та же. Если честно, некоторые вещи меня сильно не устраивают во всей этой системе (например, если загружена DLL со своими классами, они "видны" только внутри этой DLL; создать объект по имени класса а не по CRuntimeClass можно только в MFC 7.0, и др.), но идея ИМХО в целом неплоха, работоспособна и м.б. использована там где это надо. Продолжая тему, хотелось бы узнать, что скрывается под всеми этими макросами, вставляемыми в каждый класс ? А на самом деле всё достаточно просто и логично. В зависимости от того, как подключается MFC, содержимое каждого макроса может несколько видоизменяться. Далее будут приводиться только "обобщенные" версии макросов, без модификаторов "const", "AFX_DATA" и без "характерных" методов для каждого типа подключения MFC. Итак, первый макрос:
Как видим, в каждый класс, содержащий макрос DECLARE_DYNCREATE, включается статический компонент "CRuntimeClass class##class_name", где "##" означают подстановку значения аргумента макроса. Например, если мы пишем:
то получим:
Как известно, статические компонентные данные классов нужно инициализировать. Что это означает - вне класса нужно дать полное имя компонентного данного с инициализирующим значением. Именно этим (а также реализацией метода GetRuntimeClass) и "занимается" макрос IMPLEMENT_DYNAMIC (используя другой макрос - IMPLEMENT_RUNTIMECLASS):
Отдельного упоминания здесь заслуживает, разве что, "wShema", относящаяся к "супер-пупер" поддержке версий объектов (advanced versioning support), абсолютно бесполезная, имхо, фича. Остальное ясно: макрос IMPLEMENT_DYNAMIC вызывает вложенный макрос IMPLEMENT_RUNTIMECLASS, который инициализирует статический компонент "CRuntimeClass class##class_name", записывая в поля нашего объекта "classMyClassA" такие данные, как имя класса, адрес конструктора (он равен нулю, т.к. для поддержки динамического создания объектов по CRuntimeClass'у, нужно использовать DYNCREATE-макросы), и указатель на CRuntimeClass базового класса. ЗЫ, при динамическом подключении MFC вместо указателя на базовый CRuntimeClass используется указатель на функцию "_GetBaseClass", но от этого принципиально ничего не изменяется. Далее, перейдем к оставшимся двум видам макросов (мы рассмотрели "DYNAMIC") - "DYNCREATE" и "SERIAL", а также поподробнее рассмотрим, где же MFC хранит список доступных классов (да-да, оказывается есть такой "скрытый" список, хе-хе ![]() DYNCREATE-макросы добавляют возможность не только определить во время программы, какому классу принадлежит объект (эту возможность даёт рассмотренный DYNAMIC-макрос), но и создать объект определённого класса, используя специальную функцию "CreateObject", входящую в состав как CRuntimeClass'а, так и нашего "MyClassA". Как это реализуется:
Здесь всё понятно: во-первых, подключается рассмотренный макрос DECLARE_DYNAMIC, а во-вторых, в наш класс добавляется метод CreateObject.
Тут реализуется метод "CreateObject", и указатель на этот метод передаётся во вложенный макрос IMPLEMENT_RUNTIMECLASS (инициализирующий CRuntimeClass), чтобы CRuntimeClass мог создавать объекты именно нашего класса (имя которому class_name=="MyClassA"), используя указатель на CreateObject. Для полноты картины остаётся рассмотреть сериализацию (SERIAL-макросы) и глобальный список классов. Ну и что это за зверь такой - сериализация ? - спросите вы. В переводе это означает "преобразование в последовательную форму". Значит ясно, что это технология, позволяющая записывать и восстанавливать данные в/из последовательной формы (файла, потока). Для записи и чтения объектов, данных и т.п. в MFC используется довольно-таки удобный класс CArchive. У него определены операторы "<<" и ">>", смысл которых, я думаю, ясен (особенно тем, кто много работает с STL), эти операторы поддерживают все простые типы данных, плюс строки (CString), плюс - внимание - типы, определяемые пользователем. Ну да, скажете вы, как же CArchive может догадаться, как именно сохранять мой супер-пупер-навороченный объект (хранящий, помимо всего, сотню вложенных объектов разных классов и десяток контейнеров STL ![]() Да очень просто. В нашем классе "MyClassA" необходимо переопределить виртуальную функцию "Serialize", выполняющую всю работу по сохранению и загрузке внутренних данных класса "MyClassA". К примеру, если наш класс содержит только переменную типа "int", то "Serialize" будет выглядеть так:
Откуда же будет вызвана функция "Serialize" ? Из операторов ">>" и "<<". Никаких проблем с тем, что в классе CArchive не определены такие операторы для нашего класса "MyClassA", достаточно взглянуть на исходник:
Именно для поддержки оператора ">>" предназначены SERIAL-макросы, (оператор "<<" можно сделать одинаковым для всех, т.к. в отличие от ">>", на момент записи в архив известен класс объекта). Содержимое SERIAL-макросов:
Насчет DECLARE_SERIAL всё вроде ясно: включение знакомого нам DECLARE_DYNCREATE, и определение пресловутого оператора ">>". Первые два пункта IMPLEMENT_SERIAL тоже не представляют интереса (они полностью совпадают с макросом IMPLEMENT_DYNCREATE). А вот на следующих двух стоит остановиться подробнее. Оператор ">>", как видим, вызывает метод "ar.ReadObject", передавая ему CRuntimeClass нашего "MyClassA". Это необходимо для того, чтобы убедиться, что загруженный из архива объект точно принадлежит классу "MyClassA", в противном случае генерится исключение "CArchiveException::badClass" (которое можно перехватить, но это уже тема для отдельного разговора). В случае успеха, указателю "pOb" присваивается адрес загруженного объекта. В процессе загрузки используется функция "Serialize", определенная в нашем классе "MyClassA", для того чтобы "раскидать" данные из архива по полям объекта. В качестве простого примера эффективного использования механизма сериализации (вкупе с использованием STL), можно привести следующий код:
Красота списка xlist состоит в том, что в нём можно хранить указатели на объекты, принадлежащие любому классу, в любой последовательности - и не нужно заботиться о том, как правильно сохранить или загрузить каждый объект в списке, всю работу с классами объектов выполняет механизм сериализации MFC. Осталась только одна нерассмотренная "фича" - глобальный список CRuntimeClass'ов (и связанная с ним странная конструкция "AFX_CLASSINIT _init_##class_name(RUNTIME_CLASS(class_name));") Остановимся на примере из предыдущего поста (класс MyClassA, содержащий список объектов различных классов). Приведу кратко основные моменты, на которые нужно обратить внимание: - объекты в списке xlist могут принадлежать произвольным классам; - на момент чтения списка объектов из архива неизвестно, к какому классу будет относиться очередной загружаемый объект; - в архиве перед каждым объектом хранится имя класса; - соответственно, при чтении очередного объекта из архива, зная имя класса, MFC нужно каким-то образом создать объект данного класса. Данная задача была решена очень просто: с помощью глобального списка CRuntimeClass'ов, хранимого внутри объекта CModuleState. Чтобы получить доступ к этому объекту, используется функция MFC "AfxGetModuleState", возвращающая указатель на объект CModuleState, в котором определено поле "m_classList", представляющее собой простой односвязный список. Перечислить все классы, "зарегистрированные" на данный момент, можно например так:
Список классов может пригодиться, например, в случае необходимости создать объект по имени класса (не имея "под рукой" CRuntimeClass). Кстати, такую фичу (создание объекта по строковому имени класса) включили в MFC 7.0 (идущую с .NET), в новой версии CRuntimeClass'а добавлено 4 специализированных метода:
Осталось только выяснить, каким образом CRuntimeClass попадает в список классов. Оказывается, не рассмотренный до сих пор кусок макроса IMPLEMENT_SERIAL и выполняет данную работу, вызывая глобальную функцию "AfxClassInit", добавляющую наш CRuntimeClass в список доступных классов:
Вот и всё, что я хотел рассказать о "кухне" классов времени выполнения в MFC. --------------------
|
||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||
![]() ![]() ![]() |
Правила форума "С++:Общие вопросы" | |
|
Добро пожаловать!
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn |
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |