Анимация
JavaScript
|
Главная Библионтека Описания переменных класса В некоторых ситуациях объекты Class стоит сделать достаточно умными, чтобы они могли получать экземпляры представляемого класса и описывать их структуру. Все начинается с переменных класса. В наиболее прямолинейном варианте Class возвращает итератор, который в свою очередь возвращает пару смещение/Class, описывающую смещение переменной внутри экземпляра и ее Class. Имеет смысл создать перечисления для трех подмножеств переменных класса: 1 . Все переменные, включая встроенные типы (такие как int). Для представления примитивных типов вам придется создать фиктивные классы, производные от Class. 2. Только переменные невстроенных типов, для которых обычно и так существует свой Class. 3. Только указатели и ссылки на другие объекты. Последний вариант играет особенно важную роль в некоторых нетривиальных алгоритмах сборки мусора. Описания функций класса В еще более редких ситуациях объект Class должен описывать набор функций представленного им класса. При этом вы фактически начинаете играть роль компилятора С++, так что не перегибайте палку. И снова оптимальным представлением оказывается итератор. Для каждой функции можно возвращать любую информацию, от простейшей (ее имени) до более сложной (адрес, имена, типы и порядок аргументов и тип возвращаемого значения). Некоторые проблемы просто выходят за рамки С++; если вам захочется проделать нечто подобное, стоит серьезно подумать об использовании настоящего динамического языка. Коллекции экземпляров Класс Class предоставляет еще одну интересную возможность - ведение коллекции всех экземпляров класса. Это может быть либо отдельная коллекция для каждого Class, либо одна глобальная структура данных с парами (Class, экземпляр). Если выбран второй вариант и коллекция индексируется в обоих направлениях, она оказывается чрезвычайно полезной при отладке («Покажи мне все экземпляры класса х»), а также может применяться для ответов на вопросы вроде «Каков класс данного экземпляра?» без физического хранения адреса объекта Class в каждом экземпляре. Такое решение работает только для экземпляров верхнего уровня, создаваемых производящими функциями; вложенные объекты (переменные или базовые классы) в этот реестр не попадут. Статистика Показатели сыплются как из рога изобилия - количество экземпляров, в данный момент находящихся в памяти; общее количество экземпляров, созданных с момента запуска программы; статистические профили с описанием, когда и как создавались экземпляры Возможности ограничены только тем, сколько времени вы сможете им посвятить. Если вы работаете на повременной оплате, не ограничивайте себя - ведь при желании обоснование можно придумать для любого показателя. А если нет, подумайте, окупятся ли потраченные усилия. Еще несколько слов об уничтожающих функциях После долгого разговора о том, какие замечательные штуки можно проделывать с объектами классов, вернемся к уничтожающим функциям. Многие концепции, представленные в предыдущем разделе (такие как скрытые коллекции экземпляров и статистика), реализуются лишь в том случае, если вам удастся отследить время создания и уничтожения экземпляра. Конечно, для ведения статистики можно воспользоваться статистическими переменными, производящими функциями и т.д., принадлежащими целевому классу, однако методика, связанная с объектами классов, обеспечивает намного лучшую модульность. Возьмите существующий класс. Добавьте класс объекта Class. Влейте одну-две производящие функции, перемешайте с уничтожающей функцией. Поставьте на огонь статистики и доведите до кипения. Ура! Все административные средства были добавлены без модификации исходного класса. Об ихменениях придется сообщать клиентам, но если в начале работы никаких клиентов еще не было, а управляемый класс должен оставаться неизменным или его исходные тексты недоступны, нам удалось довольно многого добиться, не создавая никаких побочных эффектов для критически важного кода. Определение класса по объекту Для существующего экземпляра довольно часто требуется определить его класс. Вроде бы ничего сложного, но в действительности это очень глубокая тема. Помните, что объекты могут создаваться в стеке или в куче, внедряться в другие объекты в виде переменных или базовых классов, а также создаваться производящими функциями. Ниже описано несколько основных решений. Внедрение указателя на объект класса Самое очевидное решение - внедрять указатель на Class в любой объект, вложенный или нет. class Object { Предок всех реальных классов protected: static ObjectClass s my c1ass; Class* my c1ass; == &s my c1ass; public: Object() : my c1ass(&s my c1ass) {} Class* My Class() { return my c1ass; } class Foo : public Object protected: static FooClass s my c1ass; public: Foo() { my c1ass = &s my c1ass; } Все классы порождаются от общего предка Object, в котором определяется протокол для получения объекта Class. Вы имеете полное право использовать одни и те же имена членов на разных уровнях иерархии классов (как это сделано с s my c1ass в нашем примере). Компилятор выбирает имя, находящееся в непосредственной области действия. Более того, конструкторы выполняются в порядке «базовый класс/переменные класса/ производные классы», поэтому последний конструктор оставит my c1ass правильное значение. Эта схема позволяет всегда получить объект Class независимо от того, сколько выполнялось преобразований типа от производных к базовым классам. Издержки составляют четыре байта, необходимые для хранения указателя. Виртуальные функции не требуются, поэтому нам не придется добавлять v-таблицу в класс, обходившийся без нее. На избыточное конструирование my c1ass будут потрачены дополнительные такты процессора, но для большинства приложений это несущественно. Пожалуй, основные издержки сводятся к дополнительному коду, находящемуся в конструкторах. В более «чистом» варианте указатель на Class задается производящими функциями объекта Class: class Object { friend class Class; private: Class* my c1ass; public: Class* My Class() { reutrn my c1ass; } class Class { protected: void SetClass(Object& obj) { obj.my c1ass = this; } class Foo : public Object { ... }; class FooClass : public Class { public: Foo* make() Foo* f = new Foo; this->SetClass(f); return f; Выглядит получше, поскольку производные от Object классы и не подозревают об этих фокусах но так ли это? Недостаток этого подхода - в том, что он не работает для экземпляров Foo, объявленных в стеке или вложенных в другие классы в виде структур данных. Перед вами одна из ситуаций, в которых приходится принимать трудное решение: то ли ограничить класс только динамическими экземплярами, то ли искать более сложное решение и без того сложной проблемы. Существует еще один вариант - управлять выделением памяти и хранить адеса объекта класса прямо над самим объектом в памяти вместо того, чтобы делать его переменной класса предка. Для этого нам понадобятся приемы управления памятью, описанные в части 4. Внешние структуры данных Как упоминалось выше, вы также можете создать глобальную коллекцию с парами экземпляр/Class. Все не так скверно, как выглядит на первый взгляд, особенно если информация Class нужна только в процессе отладки и будет исключена в рабочем режиме. Если соблюдать осторожность в реализации, решение также распространяется и на такие вложенные объекты, как стековые переменные или экземпляры, хотя для этого вам понадобится соответствующая поддержка со стороны конструктора и деструктора основного класса. Нестандартные пространства памяти Другое решение, рассматриваемое в главах 15 и 16 - физическое разделение объектов по различным пространствам памяти в соответствии с их классом. Оно отличается повышенной сложностью и попросту не работает для вложенных объектов, но зато обладает впечатляющим быстродействием и малым расходом памяти. Представители В книге «Advanced C++ Programming Styles and Idioms» Джеймс Коплин (James Coplien) выдвинул особую разновидность объекта класса, который он назвал представителем (exemplar). Представитель - это объект класса, который является экземпляром представляемого им класса. Понятно? У меня от таких высказываний голова идет кругом. class Exemplar {}; Символический класс class Foo { private: Foo(); И все остальные конструкторы, кроме одного public: Foo(Exemplar); Конструктор представителя Foo* make(); Производящие функции представителя extern Foo* foo; Сам представитель Подробности несущественны. Важно то, что один экземпляр (на который ссылается переменная foo) выполняет функции объекта класса для класса Foo. В реализациях производящих функций необходимо убедиться, что this совпадает с представителем, поскольку производящая функция не должна вызываться для всех остальных экземпляров класса. 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 [ 54 ] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |