Пример размещения динамического объекта
Последний пример программы даст вам возможность приобрести некоторые навыки в использовании размещенных в динамической памя- ти объектов, включая использование для удаления объекта деструк- тора. Программа показывает, как в динамической памяти может быть создан связанный список рабочих объектов и как он за ненадоб- ностью может быть очищен при помощи деструктора.
Построение связанного списка объектов требует, чтобы каждый объект содержал указатель на следующий объект списка. Тип TEmployee не содержит таких указателей. Простым выходом из этой ситуации было бы добавление указателя в TEmployee , благодаря че- му можно быть уверенным, что все потомки TEmployee наследуют та- кой указатель. Однако, добавление чего-либо в TEmployee требует от вас наличия исходного кода, а как говорилось ранее, одним из преимуществ объектно-ориентированного программирования является возможность расширения объектов без необходимости их перекомпиля- ции.
Решение, которое не требует никаких изменений TEmployee, создает новый тип объекта, не являющийся потомком TEmployee. Тип StaffList представляет собой очень простой объект, целью которого является создание заголовков для объектов типа TEmployee. Так как TEmployee не содержит никаких указателей на следующий объект в списке, то простой тип записи TNode осуществляет этот сервис. TNode даже проще, чем StaffList в том, что TNode не является объ- ектом, не содержит ни одного метода и не имеет никаких данных, за исключением указателя на тип TEmployee и указателя на следующий узел списка.
TStaffList содержит метод, который позволяет ему добавлять нового рабочего в связанный список записей TNode путем внесение нового экземпляра TNode непосредственно после самого себя в ка- честве указуемого с помощью указателя поля TNodes. Метод Add при- нимает указатель на объект типа TEmployee, но не сам объект. Из-за расширенной совместимости типов Турбо Паскаля указатели на любого потомка типа TEmployee также должны передаваться в TList.Add в параметре Item.
Программа WorkList описывает статическую переменную Staff типа TStaffList и строит связанный список из пяти узлов. Каждый узел указывает на отдельный рабочий объект, который является либо TEmployee, либо одним из его потомков. Перед созданием каждого динамического объекта и после того, как объект создан, возвращает число байт свободной динамической памяти. Наконец, полная струк- тура, включающая пять записей TNode и пять объектов типа TEmployee, очищается и удаляется из динамической памяти с помощью одного вызова деструктора статического объекта Staff типа TStaffList.
|
List | Node Node Node +-------+ +----+----+ +----+----+ +----+----+ | | | | | | | | | | | | | O--+----> | O | O-+---> | O | O-+----> | O | O-+---+ | | | | | | | | | | | | | | | | +-------+ +--+-+----+ +--+-+----+ +--+-+----+ | | | | | --+-- v v v --- | +-------------+ +-------------+ +-------------+ - | Name | | Name | | Name | | +-------------| +-------------| +-------------| | Title | | Title | | Title | | +-------------| +-------------| +-------------| | Rate | | Rate | | Rate | | +-------------| +-------------| +-------------| | | | | | | Сегмент | Динамически распределяемая область данных памяти (динамический) (статические| объекты) |
Рис. 9.2 Схема структур данных программы ListDemo.
Удаление сложной структуры данных из динамической памяти
Деструктор Staff.Done стоит того, чтобы рассмотреть его вни- мательно. Уничтожение объекта TStaffList включает удаление трех различных типов структур: полиморфических объектов рабочих струк- тур в списке, записей TNode, поддерживающих список, и (если он размещен в динамической памяти) объект TList, который озаглавли- вает список. Весь процесс запускается путем единственного вызова деструктора объекта TStaffList:
Staff.Done;
Код деструктора заслуживает более подробного изучения:
destructor StaffList.Done; var N: TNodePtr; begin while TNodes <> nil do begin N := TNodes; Disрose(N^.Item, Done); TNodes := N^.Next; Disрose (N); end; end;
Список очищается начиная с "головы" списка с помощью алго- ритма "из руки в руку", который до некоторой степени напоминает дерганье за веревку воздушного змея: два указателя (указатель TNodes внутри Staff и рабочий указатель N) изменяют свои ссылки в списке, тогда как первый элемент списка удаляется. Вызов процеду- ры Disрose освобождает память, занимаемую первым объектом TEmployee в списке (Item^), затем TNodes продвигается на следую- щую запись списка с помощью оператора TNodes := N^.Next, сама за- пись TNode удаляется, и процесс продолжается до полного очищения списка.
Важным моментом в деструкторе Done является способ, которым удаляются из списка объекты TEmployee:
Disрose(N.Item, Done);
Здесь N.Item является первым объектом TEmployee в списке, а вызываемый метод Done является деструктором этого объекта. Запом- ните, что действительный тип N^.Item^ не обязательно является ти- пом TEmployee, однако он может быть любым дочерним типом типа TEmployee. Очищаемый объект является полиморфическим и поэтому нельзя сделать никаких предположений относительно его действи- тельного размера или точного его типа на этапе компиляции. В при- веденном выше вызове Disрose, как только Done выполнит все содер- жащиеся в нем операторы, "невидимый" код эпилога ищет размер реа- лизации очищаемого объекта в ТВМ этого объекта. Метод Done пере- дает размер процедуре Disрose, которая затем освобождает точное количество динамической памяти, в действительности занимаемой по- лиморфическим объектом.
Помните, что если должно освобождаться правильное количество динамической памяти, то полиморфический объект должен очищаться только посредством вызова передаваемого Disрose деструктора.
В примере программы Staff объявляется как статическая пере- менная в сегменте данных. Staff мог бы столь же легко разместить- ся в динамической памяти и "прикрепиться к реальному миру" пос- редством указателя типа ListPtr. Если заголовок списка также яв- ляется динамическим объектом, то удаление структуры можно осу- ществить путем вызова деструктора, выполняющегося внутри Disрose:
var Staff: TStaffListPtr; begin Disрose(Staff, Done); . . .
Здесь процедура Disрose вызывает метод деструктора Done для очистки структуры в динамической памяти. Затем, когда Done завер- шается, Disрose освобождает память, на которую указывает Staff, удаляя, как правило, из динамической памяти также и заголовок списка.
Программа WORKLIST.PAS (находящаяся на вашем диске) исполь- зует тот же модуль WORKERS.PAS, что и раньше Она создает объект List, являющийся оглавлением связанного списка из пяти полиморфи- ческих объектов, совместимых с TEmployee, а затем удаляет всю ди- намическую структуру данных с помощью единственного вызова дест- руктора Staff.Done.