235
};
void MainLoop()
{
Operation*
op;
while ((op = Operation::OperationQ.Pop()) != NULL)
{
op->DoSomething();
object_space->Copy1();
}
}
Если теперь объект захочет выполнить какое-нибудь действие, он не выполняет его сам, а создает
класс, производный от
Operation
, и заносит его в очередь. Если обработка связана с итерациями,
объект
Operation
продолжает направлять себя в очередь в конце каждого вызова
DoSomething()
до
завершения итераций. Приведу краткий набросок традиционного подхода и опосредованной
архитектуры:
// Традиционный способ сделать что-то
void Foo::SomeOperation()
{
for
(...)
OnePass();
}
Если операция занимает много времени, перед вами возникают два неудобных варианта: не выполнять
сборку мусора и уплотнение до завершения
Foo::SomeOperation()
, а следовательно, утратить
многие преимущества от управления памятью; или косвенно вызвать
Copy1()
во время вызова
Foo::SomeOperation()
, а это небезопасно. Очередь операций предоставляет другое решение
проблемы:
// Опосредованная архитектура с очередями операций
class FooSomeOperation : public Operation }
public:
virtual void DoSomething();
{
//
Выполнить один проход
if
(еще не готово)
this->Post();
//
Послать заново для следующего захода
delete
this;
}
};
void Foo::SomeOperation()
{
Operation* op = new FooSomeOperation(args);
op->Post();
//
op->DoSomething
выполняет работу
}
Теперь функция
Copy1()
заведомо не будет вызвана в момент нахождения в стеке
FooSomeOperation::DoSomething()
. Подобные очереди операций так часто приносят пользу, что
являются едва ли не стандартной возможностью объектно-ориентированных библиотек классов.
Сколько библиотек — столько и вариаций (скажем, назначение приоритетов операций или
возможность блокирования одних операций до завершения других), но во всех разновидностях
встречается одна общая черта — максимальное освобождение стека на время периодического
выполнения вспомогательных операций.
236
Адреса переменных класса
Аналогичная проблема возникает и при получении адреса переменной класса, даже если это
происходит в функциях класса, которым мы управляем. Именно по этой причине мы и потребовали,
чтобы везде применялись дескрипторы. Благодаря опосредованной методике проблем не возникает —
при условии, что вы получаете адрес переменной класса, используете и забываете про него в течение
одного цикла. Впрочем, от хлопот, связанных с опосредованной архитектурой, можно и отказаться.
Если вы абсолютно уверены, что адрес не сохранится до следующего вызова функции
Copy1()
, то
можете избирательно снимать требование обязательного применения дескрипторов.
Множественное наследование
Множественное наследование безопасно при условии соблюдения всех приведенных выше
рекомендаций по уходу и кормлению указателя
this
. То обстоятельство, что
this
пляшет в памяти
при вызове функций второго и третьего базового класса, не вызовет новых проблем — это все та же
проблема с
this
, только замаскированная. Конечно, вы никогда не должны возвращать адрес объекта,
преобразованного к базовому классу, но передача
this
тоже небезопасна.
Неустойчивые объекты
Объекты, адреса которых (в отличие от адресов их ведущих указателей) передаются за пределы вашей
зоны контроля — скажем, при вызове системной функции — необходимо сначала вывести из
очищаемого пространства. Самое простое решение — создать объект-операцию, которая перемещает
свой объект и вызывает системную функцию, когда оказывается в новом безопасном месте.
Уплотнение на месте
Очевидный недостаток алгоритма Бейкера заключается в напрасной потере половины памяти.
Существует и другой, мене очевидный недостаток — при каждом проходе все объекты копируются из
одного места памяти в другое. Такое копирование может отрицательно повлиять на быстродействие
программы. Обе проблемы решаются в другом алгоритме, который называется уплотнением на месте
(compaction in place). Вместо двух половин существует единое пространство, а в процессе уплотнения
все объекты смещаются вниз. На следующей диаграмме показано состояние памяти до и после
уплотнения.
До
После
Копирование объектов должно происходить в правильном порядке, снизу вверх, в противном случае
объекты будут накладываться друг на друга. Этого можно добиться двумя способами: отсортировать
ведущие указатели перед началом перебора или изначально хранить их в отсортированном порядке.
Хранить ведущие указатели в двусвязном списке, отсортированном по адресу указываемого объекта,
довольно просто — при условии, что вы готовы потратить лишнюю пару слов для указателей на
следующий и предыдущий элемент. Шаблон ведущего указателя и дескрипторы аналогичны тем,
которыми мы пользовались до настоящего момента. Базовый класс
VoidPtr
был усовершенствован
для хранения экземпляров в связанном списке.
Базовый класс VoidPtr
Память под объекты всегда выделяется снизу вверх. Если новые объекты
VoidPtr
всегда будут
добавляться в конец связанного списка, то список всегда будет отсортирован по возрастанию адресов
Do'stlaringiz bilan baham: |