Информация о классе
В объектах класса удобно хранить сведения о самом классе. Для этого лучше всего создать гомоморфный базовый класс для объектов классов.
class Class {
protected:
Collection<Class> base_classes;
Collection<Class> derived_classes;
String class_name;
Class() {}; // Класс становится абстрактным базовым
public:
// Функции доступа для получения имени и т.д.
};
Состав хранимой информации в огромной степени зависит от потребностей приложения и от того, насколько увлеченно вы занимались SmallTalk на этой неделе.
Имя класса и создание экземпляров по имени
Базовая информация, которую может сообщить объект класса, - имя класса в виде некоторой символьной строки. Кроме того, можно хранить словарь всех объектов классов, индексируемый по имени класса. Если добавить к этому универсальный интерфейс к производящей функции, вам удастся реализовать возможность, которая не поддерживается в С++ напрямую - создание экземпляров по имени (instantiate by name).
// Где-то в клиентской программе
Class* c = gClasses.Find("Grandpa");
???* g = (Grandpa*)c->make(???);
Как видите, практическая польза такого подхода ограничивается некоторыми проблемами. Если мы уже знаем, что создается экземпляр Grandpa, то создание экземпляра по имени выглядит неразумно - нам будет трудно определить, к какому классу выполняется преобразование. Вдобавок данная схема не позволяет предоставить отдельные сигнатуры для производящих функций производных классов. Тем не менее, в некоторых ситуациях такая методика оказывается чрезвычайно полезной. Предположим, вы сохранили объект в виде потока байтов и теперь загружаете его. Если первые n байт содержит имя класса в виде символьной строки, вы сможете найти нужный объект класса для создания экземпляра.
Как правило, реализация заканчивается созданием во всех классах Class функции make(istream&) или ее эквивалента. Программа приобретает следующий вид:
// В коде чтения потока
cin << className;
Class* c = gClasses.Find(className);
BasClass* obj = c->make(cin);
Иерархия классов
Сведения об иерархии классов можно хранить по разному, но в конечном счете все сводится к структурам данных с экземплярами Class. Выше был представлен один из вариантов: вести в каждом Class две коллекции, по одной для базовых и производных классов. Конечно, это следует понимать условно - речь идет об иерархии не объектов Class, а представленных ими классов. Необходимость различать понятия Class и «класс» наверняка вызовет у вас головную боль, но у поклонников SmallTalk и Lisp это считается хорошим развлечением и признаком мастерства. Другой способ - ввести одну глобальную структуру данных с парами (базовый, производный), индексируемую в обоих
направлениях. В некоторых ситуациях вместо пар используются триплеты (базовый, производный, порядок), чтобы базовые классы перечислялись в порядке их объявления в соответствующем классе.
Описания переменных класса
В некоторых ситуациях объекты Class стоит сделать достаточно умными, чтобы они могли получать экземпляры представляемого класса и описывать их структуру. Все начинается с переменных класса. В наиболее прямолинейном варианте Class возвращает итератор, который в свою очередь возвращает пару смещение/Class, описывающую смещение переменной внутри экземпляра и ее Class. Имеет смысл создать перечисления для трех подмножеств переменных класса:
1. Все переменные, включая встроенные типы (такие как int). Для представления примитивных типов вам придется создать фиктивные классы, производные от Class.
2. Только переменные невстроенных типов, для которых обычно и так существует свой Class.
3. Только указатели и ссылки на другие объекты.
Последний вариант играет особенно важную роль в некоторых нетривиальных алгоритмах сборки мусора.
Описания функций класса
В еще более редких ситуациях объект Class должен описывать набор функций представленного им класса. При этом вы фактически начинаете играть роль компилятора С++, так что не перегибайте палку. И снова оптимальным представлением оказывается итератор. Для каждой функции можно возвращать любую информацию, от простейшей (ее имени) до более сложной (адрес, имена, типы и порядок аргументов и тип возвращаемого значения). Некоторые проблемы просто выходят за рамки С++; если вам захочется проделать нечто подобное, стоит серьезно подумать об использовании настоящего динамического языка.
Коллекции экземпляров
Класс Class предоставляет еще одну интересную возможность - ведение коллекции всех
экземпляров класса. Это может быть либо отдельная коллекция для каждого Class, либо одна глобальная структура данных с парами (Class, экземпляр). Если выбран второй вариант и коллекция индексируется в обоих направлениях, она оказывается чрезвычайно полезной при отладке («Покажи мне все экземпляры класса x»), а также может применяться для ответов на вопросы вроде «Каков класс данного экземпляра?» без физического хранения адреса объекта Class в каждом экземпляре. Такое решение работает только для экземпляров верхнего уровня, создаваемых производящими функциями; вложенные объекты (переменные или базовые классы) в этот реестр не попадут.
Статистика
Показатели сыплются как из рога изобилия - количество экземпляров, в данный момент находящихся в памяти; общее количество экземпляров, созданных с момента запуска программы; статистические профили с описанием, когда и как создавались экземпляры… Возможности ограничены только тем, сколько времени вы сможете им посвятить. Если вы работаете на повременной оплате, не ограничивайте себя - ведь при желании обоснование можно придумать для любого показателя. А если нет, подумайте, окупятся ли потраченные усилия.