Алекс Jenter - Программирование на Visual C++. Архив рассылки
- Название:Программирование на Visual C++. Архив рассылки
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Алекс Jenter - Программирование на Visual C++. Архив рассылки краткое содержание
РАССЫЛКА ЯВЛЯЕТСЯ ЧАСТЬЮ ПРОЕКТА RSDN, НА САЙТЕ КОТОРОГО ВСЕГДА МОЖНО НАЙТИ ВСЮ НЕОБХОДИМУЮ РАЗРАБОТЧИКУ ИНФОРМАЦИЮ, СТАТЬИ, ФОРУМЫ, РЕСУРСЫ, ПОЛНЫЙ АРХИВ ПРЕДЫДУЩИХ ВЫПУСКОВ РАССЫЛКИ И МНОГОЕ ДРУГОЕ.
Программирование на Visual C++. Архив рассылки - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Чтобы осуществить идентификацию объектов во время выполнения, MFC создает в приложении реестр классов, унаследованных от CObject . Этот реестр никак не связан с OLE-реестром, но их концепции схожи. Реестр типов представляет собой связанный список структур CRuntimeClass , в котором каждая структура описывает один класс-наследник CObject . На листинге 1 показано внутреннее устройство структуры CRuntimeClass .
Листинг 1
struct CRuntimeClass {
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // номер схемы загруженного класса
void (PASCAL* m_pfnConstruct)(void* p); // NULL => abstract class
CRuntimeClass* m_pBaseClass;
// Operations
CObject* CreateObject();
// Implementation
BOOL ConstructObject(void* pThis);
void Store(CArchive& ar);
static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);
// объекты CRuntimeClass, связанные в простой список
CRuntimeClass* m_pNextClass;// список зарегистрированных классов
};
Весь фокус в том, что типы из этого реестра не прописаны ни в одной таблице. Первый ключ к разгадке этого феномена находится в начале файла SCRIBDOC.H из MFC-примера "Scribble". Начало объявления класса выглядит так, как показано в примере 1(a).
Пример 1: (a) Начало объявления класса; (b) после обработки препроцессором макрос DECLARE_DYNCREATE разворачивается в несколько новых членов класса.
(a)
class CScribDoc : public CDocument {
protected:
// создавать только при сериализации
CScribDoc();
DECLARE_DYNCREATE(CScribDoc)
...
};
(b)
protected:
static CRuntimeClass* __stdcall _GetBaseClass();
public:
static CRuntimeClass classCScribDoc;
virtual CRuntimeClass* GetRuntimeClass() const;
static void__stdcall Construct(void* p);
В документации сказано, что макрос DECLARE_DYNCREATE позволяет классам-наследникам CObject создаваться динамически во время выполнения. Хотя это определение абсолютно верно, то, что происходит внутри этого макроса, гораздо интереснее. После обработки препроцессором макрос DECLARE_DYNCREATE разворачивается в несколько новых членов класса, как показано в примере 1(b) (Все примеры взяты из MFC 3.1 и Visual C++ 2.1. Я выровнял код, сгенерированный препроцессором, для повышения читабельности).
Идентификация типов времени выполнения в 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).
Читать дальшеИнтервал:
Закладка: