|
Программирование >> Синтаксис инициирования исключений
Невидимые ведущие указатели Чтобы не использовать шаблоны дескрипторов и ведущих указателей, можно было организовать множественное наследование ведущих указателей от VoidPtr и гомоморфного базового класса. Иначе говоря, ведущие указатели становятся невидимыми, как объяснялось в предыдущих главах. В игру вступают все механизмы, сопутствующие идиоме невидимых указателей (например, производящие функции). class Foo { public: static Foo* make(); Возвращает пару указатель-указываемый объект virtual void Member1() = 0; И т.д. для открытого интерфейса В файле foo.cpp class FooPtr : public Foo, public VoidPtr { public: FooPtr(Foo* foo, size t size) : VoidPtr(foo, size) {} virtual ~FooPtr() { delete (Foo*)address; } virtual void Member1() { ((Foo*)address)->Member1(); } И т.д. для остальных открытых функций class RealFoo : public Foo { ... }; Foo* Foo::make() return new FooPtr(new RealFoo, sizeof(RealFoo)); В клиентском коде class Bar { private: Foo* foo; На самом деле невидимый указатель public: Bar() : foo(Foo::make()) {} Такое решение улучшает инкапсуляцию применения ведущих указателей. Кроме того, оно позволяет производящим функциям решить, какие экземпляры должны управляться подобным образом, а какие - с помощью обычных невидимых указателей или даже вообще без указателей. Все прекрасно работает, пока вы соблюдаете осторожность и выделяете в VoidPtrPool достаточно места для FooPtr . Помните, что из-за множественного наследования по сравнению с VoidPtr размер увеличивается, по крайней мере, на v-таблицу. Объекты классов Возможен и другой вариант - потребовать, чтобы все объекты происходили от общего класса-предка, способного возвратить объект класса для данного объекта или, по крайней мере, размер объекта. В этом случае вам уже не придется хранить в указателе объект экземпляра, поскольку его можно будет получить у объекта класса. Если вы готовы смириться с некоторым насилием в отношении типов в дескрипторах, это также позволит вам избежать шаблонов второго уровня, используемых для главных указателей. Вместо void* в VoidPtr можно будет хранить CommonBase* (где CommonBase - общий базовый класс). Мы избавляемся от переменной size, от необходимости иметь шаблон, производный от VoidPtr, и от виртуального деструктора в VoidPtr, и как следствие - от 4-байтового адреса v-таблицы. С другой стороны, если управляемые объекты уже содержат v-таблицы и не принуждают вас к применению множественного наследования, дополнительных затрат не будет. Оптимизация в особых ситуациях Если адрес переменной класса получать не требуется, ее можно хранить в виде внедренного объекта. Впрочем, как показывает следующий фрагмент, ситуация не всегда находится под контролем разработчика класса: void f(int); class Foo { private: int x; Адрес получать не нужно, поэтому храним непосредственно public: void F() { f(x); } Вышядит вполне безопасно, не правда ли? А теперь предположим, что автор функции f() привел ее интерфейс к следующему виду: void f(int&); И вот вся тщательно спроектированная оптимизация обрушивается вам на голову. У внедренных объектов есть еще одна проблема: вы должны проследить не только за тем, чтобы никогда не получать адрес объекта, но и за тем, чтобы никогда не получать адресов рекурсивно внедренных членов. class Bar { private: Foo foo; Допустим, вы сможете доказать, что ни одна из функций Bar не получает адрес foo. Но вам придется сделать следующий шаг и проследить еще и за тем, чтобы все функции Foo тоже были безопасными. Та же логика относится и к базовым классам. Важно понимать, что такая оптимизация должна осуществляться на уровне всей программы, а не только проектируемого класса. Алгоритм Бейкера Один из алгоритмов уплотнения жертвует неимоверным количеством (а точнее, половиной) памяти в интересах скорости. Процесс уплотнения понемногу вплетается в обычную работу программы. Этот алгоритм называется алгоритмом Бейкера (Bakers Algorithm). Пул памяти делится на две половины, A и B. В любой момент времени одна из этих половин является активной (то есть в ней создаются новые объекты). Память выделяется снизу вверх, а в момент удаления объекта не делается никаких попыток вернуть занимаемую им память. Время от времени все активные объекты копируются из одной половины памяти в другую. В процессе копирования автоматически происходит уплотнение нижней части новой активной половины. Активным называется объект, для которого в стане ведущих указателей найдется ссылающийся на него ведущий указатель (в нашем случае VoidPtr). Пространства объектов Половины представлены в виде пространств памяти для создания объектов. Класс HalfSpace изображает одну половину, а Space - всю память, видимую клиентам. Класс HalfSpace Каждая половина по отдельности выглядит как обычное пространство памяти со специализированной функцией Allocate (). Парная функция Dea11ocate() не понадобится. class HalfSpace { private: unsigned long next byte; Следующий выделяемый байт unsigned char bytes[HALFSIZE]; public: Ha1fSpace() : next byte(0) {} void* Al1ocate(size t size); void Reinitia1ize() { next byte = 0; } void* Ha1fSpace::Al1ocate(size t size) Выровнять до границы слова size = ROUNDUP(size); if (next byte + size >= HALFSIZE) return NULL; Не хватает памяти void* space = &bytes[next byte]; next byte += size; return space; Класс Space Общий пул представляет собой совокупность двух половин. Он также имеет функцию Al1ocate(), которая в обычных ситуациях просто поручает работу активной половине. Если в активной половине не найдется достаточно памяти, происходит переключение половин и копирование активных объектов в другую половину функцией Swap() . Эта схема основана на предыдущем материале - специализированном пуле VoidPtr со средствами перебора. class Space { private: HalfSpace A, B; HalfSpace* active; HalfSpace* inactive; void Swap(); Переключить активную половину, скопировать объекты public: Space() : active(&A), inactive(&B) {}; void* Al1ocate(size t size); void* Space::Al1ocate(size t size) void* space = active->Al1ocate(size); if (space != NULL) return space; Swap(); Space = active->Al1ocate(size); if (space == NULL) Исключение - нехватка памяти return space; void Space::Swap() if (active == &A) active = &B; inactive = &A; else
|
© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки. |