Последний пример программы даст вам возможность приобрести некоторые навыки в использовании размещенных в динамической памяти объектов, включая использование для удаления объекта деструктора. Программа показывает, как в динамической памяти может быть создан связанный список рабочих объектов и как он за ненадобностью может быть очищен при помощи деструктора.
Построение связанного списка объектов требует, чтобы каждый объект содержал указатель на следующий объект списка. Тип 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.
Удаление сложной структуры данных из динамической памяти
Деструктор 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.
8 8 8
| |