|
|||||||||||||||||||
|
Динамические объектыВсе примеры объектов, рассмотренные до сих пор, имели статические экземпляры типов объектов, которые были перечислены в объявлениях var и размещены в сегменте данных и в стеке. var ACircle : Circle; Примечание: Использование слова "статический" не связано никаким образом со статическими методами. Объекты могут быть размещены в куче и можно ими манипулировать с помощью указателей, это же имеет место и для тесно связанных с ними типов записей, которые всегда были в Паскале. Turbo Pascal включает несколько мощных расширений для обеспечения более легкого и эффективного размещения и освобождения объектов. Объекты можно разместить как указатель, на который ссылается процедура New: var PCircle := ^Circle; New(PCircle); Так же как и для типов записей, New выделяет объем памяти в куче для размещения экземпляра базового типа указателя и возвращает адрес выделенного объема в указателе. Если динамический объект содержит виртуальные методы, то его нужно затем проинициализировать с помощью вызова констрактора перед любыми вызовами методов этого объекта: PCircle^.Init(600,100,30); Вызовы методов затем могут осуществляться обычным образом с помощью имени указателя и символа ссылки ^(знак вставки), используемых вместо имени экземпляра, которое применялось бы в вызове статически размещенного объекта: OldXPosition := PCircle^.GetX;
Размещение и инициализация с помощью New.Turbo Pascal расширяет синтаксис New, чтобы использовать более компактные и удобные способы распределения памяти для объекта, находящегося в куче и инициализации объекта с помощью одной операции. New может теперь вызываться с помощью двух параметров: в качестве первого параметра используется имя указателя, а второго параметра - вызов констрактора: New(PCircle, Init(600,100,30)); Когда для New используется такой расширенный синтаксис, констрактор Init фактически выполняет динамическое распределение, используя специальный код ввода, генерируемый как часть компиляции констрактора. Имя экземпляра не может предшествовать Init, так как во время вызова New экземпляр, инициализируемый с помощью Init, еще не существует. Компилятор идентифицирует правильный метод Init, который нужно вызывать, с помощью указателя, передаваемого в качестве первого параметра. New так же была расширена, чтобы она действовала как функция, возвращающая значение указателя. Параметр, передаваемый New, является типом указателя на объект, а не переменной указателя. type ArcPtr = ^Arc; var PArc : ArcPtr; PArc := New(ArcPtr); Заметим, что расширение New до функциональной формы применяется ко всем типам данных, а не только к типам объектов. type CharPtr = ^Char; {Char - это не тип объекта ...} var PChar : CharPtr; PChar := New(CharPtr); Функциональная форма New, так же как и процедурная форма, может принимать констрактор типа объекта в качестве второго параметра: PArc := New(ArcPtr, Init(600,100,25,0,90)); В Turbo Pascal было параллельно определено расширение Dispose, которое объясняется в следующих разделах. Примечание: Новая стандартная процедура Fail поможет Вам обнаружить ошибку в констракторах. См. раздел "Восстановление ошибок констрактора" в главе 17.
Освобождение динамических объектов.Так же как и традиционные записи Паскаля объекты, размещенные в куче, могут быть освобождены с помощью Dispose, когда они уже больше не нужны: Dispose(PCircle); Тем не менее избавление от ненужного динамического объекта может включать не только освобождение его пространства в куче. Объект может содержать указатель на динамические структуры или объекты, которые необходимо освободить или "очистить" в определенном порядке, особенно когда они включают сложные динамические структуры данных. То, что нужно сделать для того, чтобы очистить динамический объект в определенном порядке, нужно объединить в один метод так, чтобы объект можно было исключить с помощью одного вызова метода: MyComplexObject.Done; Метод Done должен объединять все детали исключенного объекта и все структуры данных и объекты, вложенные в данный объект. Примечание: Идентификатор Done используется для методов очистки, которые "закрывают магазин" после того, как объект становится ненужным. Это законно и часто используется, чтобы определить многочисленные методы очистки для заданного типа объекта. Возможно, что сложные объекты нужно будет очищать различными способами в зависимости от того, как они были размещены или использованы, или в зависимости от того, в каком режиме или состоянии был объект, когда он удаляется.
Дестракторы.Turbo Pascal предоставляет специальный тип метода, называемый дестрактором, для очистки и удаления динамически распределенных объектов. Дестрактор комбинирует шаг освобождения памяти в куче с некоторыми другими задачами, которые необходимы для данного типа объекта. Аналогично другим методам для одного типа объектов, можно определить несколько дестракторов. Дестрактор определяется вместе со всеми другими методами объекта в определении типа объекта: Point = object(Location) Visible : Boolean; Next : PointPtr; constructor Init(InitX, InitY : Integer); destructor Done; virtual; procedure Show; virtual; procedure Hide; virtual; function IsVisible : Boolean; procedure MoveTo(NewX, NewY : Integer); procedure Drag(DragBy : Integer); virtual; end; Дестракторы можно наследовать и они могут быть или статическими, или виртуальными. Так как для различных типов объектов обычно требуются различные задачи освобождения памяти, мы рекомендуем, чтобы дестракторы всегда были виртуальными так, чтобы в любом случае выполнялся бы соответствующий для данного типа объекта дестрактор. Запомните, что зарезервированное слово destructor нужно для любого метода освобождения памяти даже, если определение данного типа объекта содержит виртуальные методы. Дестракторы фактически действуют только на динамически распределенные объекты. При очистке динамически распределенного объекта дестрактор выполняет следующее: он гарантирует, что всегда будет освобождаться правильное количество байтов в куче. Тем не менее нет вреда при использовании дестракторов со статически распределенными объектами; фактически, если тип объекта не имеет дестрактора, то объекты этого типа лишаются преимущества динамического управления памятью Turbo Pascal. Действие дестракторов наиболее проявляется когда очищаются полиморфные объекты и когда освобождается память, занимаемая этими объектами в куче. Полиморфный объект - это объект, который был назначен родительскому типу на основании правил расширенной совместимости типов Turbo Pascal. В примере с графическими фигурами экземпляр объекта типа Circle, присвоенный переменной типа Point, является примером полиморфного объекта. Эти правила применяются так же и для указателей. Указатель на Circle можно свободно присвоить указателю на тип Point и содержимое этого указателя так же будет полиморфным объектом. Термин "полиморфный" является подходящим для этого случая, потому что код, использующий такой объект, не знает точно во время компиляции какой тип объекта находится в конце строки, а знает только то, что этот объект будет одним из объектов, порожденных заданным типом. Очевидно, что размеры типов объектов различаются. Поэтому когда приходит время очищать полиморфный объект, размещенный в куче, то возникает вопрос, как Dispose узнает сколько байтов в куче нужно освободить? Во время компиляции из полиморфного объекта нельзя получить никакой информации о размере объекта. Дестрактор решает проблему посредством обращения к месту, где эта информация хранится: в переменных таблицы виртуальных методов данного экземпляра. В каждой таблице виртуальных методов есть размер в байтах типа объекта, к которому относится эта таблица. Таблица виртуальных методов для любого объекта доступна с помощью неявного параметра Self, передаваемого методу при любом его вызове. Дестрактор - это просто особый тип метода и при вызове он получает копию Self в стеке. Поэтому в то время, как объект может быть полиморфным во время компиляции, он никогда не может быть полиморфным во время выполнения благодаря позднему связыванию. Чтобы выполнить освобождение памяти при позднем связывании, дестрактор нужно вызвать как часть расширенного синтаксиса для процедуры Dispose: Dispose(PPoint, Done); (Вызов дестрактора за пределами вызова Dispose не выполняет автоматическое освобождение). В приведенном примере дестрактор объекта, определенного указателем PPoint, выполняется как обычный вызов метода. Тем не менее при выполнении дестрактор ищет размер типа экземпляра в таблице виртуальных методов этого экземпляра и передает этот размер Dispose. Dispose завершает выполнение посредством освобождения правильного количества байтов в куче, принадлежащих ранее PPoint^. Освобождаемое количество байтов будет правильным, если PPoint указывает на экземпляр типа Point или экземпляр любого типа, порожденного Point, например, Circle или Arc. Заметим, что метод дестрактора сам может быть пустым и все равно выполнять свою функцию: destructor AnObject.Done; begin end; Полезную работу в этом дестракторе выполняет не тело метода, а эпилоговый код, генерируемый компилятором в ответ на зарезервированное слово destructor. В этом дестрактор подобен модулю, который не экспортирует ничего, но выполняет некоторую "неявную" функцию посредством выполнения раздела инициализации перед запуском программы. Все действие происходит неявно.
Пример распределения динамического объекта.Последний пример программы дает некоторый практический опыт в использовании объектов, размещаемых в куче, включая использование дестракторов для освобождения объектов. Программа показывает как в куче может быть создан связанный список графических объектов и как можно освобождать память посредством использования вызовов дестрактора, когда объект уже не нужен. Построение связанного списка объектов требует, чтобы каждый объект содержал указатель на следующий объект в списке. Тип Point не содержит такого указателя. Наиболее легкий выход из положения состоит в добавлении указателя к Point, это обеспечивает то, что все типы, порожденные Point так же наследуют этот указатель. Тем не менее, добавление указателя к Point требует, чтобы был исходный код Point, а как упоминалось выше, одно из преимуществ объектно-ориентированного программирования состоит в возможности расширения существующих объектов без необходимости перекомпиляции исходного кода. Решение, которое не требует изменения в Point, заключается в создании нового типа объекта, не порождаемого Point. Тип List - это очень простой объект, назначение которого находится в вершине списка объектов Point. Так как Point не содержит указателя на следующий объект в списке, простой тип записи Node выполняет эту функцию. Node еще проще, чем List, так как это не объект и Node не имеет методов и не содержит данные, за исключением указателя на тип Point и указателя на следующий узел в списке. List имеет метод, который позволяет добавлять новые фигуры в связанный список записей Node посредством вставки нового экземпляра Node сразу после него в виде содержимого полей указателей на записи Node данного экземпляра. Метод Add использует в качестве параметра указатель на объект Point, а не сам объект Point. Благодаря расширенной совместимости типов в Turbo Pascal указатели на любой тип, порожденный Point, так же могут передаваться в параметр Item метода List.Add. Программа ListDemo объявляет статическую переменную AList типа List и создает связанный список с тремя узлами. Каждый узел указывает на определенную графическую фигуру, которая или имеет тип Point, или является одним из потомков Point. Количество байтов в свободном пространстве кучи сообщается перед созданием любого динамического объекта и затем еще раз после того, как все объекты будут созданы. В заключение, вся структура, включающая три записи Node и три объекта Point очищается и удаляется из кучи с помощью единственного вызова дестрактора для AList статического объекта типа List. Рисунок 1.2 Схема структур данных программы ListDemo.
│
Список │ Узел Узел Узел ┌───────┐ ┌────┬────┐ ┌────┬────┐ ┌────┬────┐ │ │ │ │ │ │ │ │ │ │ │ │ │ O──┼────Ў │ O │ O─┼───Ў │ O │ O─┼────Ў │ O │ O─┼───┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───────┘ └──┼─┴────┘ └──┼─┴────┘ └──┼─┴────┘ │ │ │ │ │ ───┴─── ∙ ∙ ∙ ───── │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐─── │ X │ │ X │ │ X │ ─ │ ├─────────────┤ ├─────────────┤ ├─────────────┤ │ Y │ │ Y │ │ Y │ │ ├─────────────┤ ├─────────────┤ ├─────────────┤ │ Visible │ │ Visible │ │ Visible │ │ ├─────────────┤ ├─────────────┤ ├─────────────┤ │ │ │ │ │ │ Сегмент │ "Куча" данных (динамические (статические │ объекты) объекты) │
Освобождение сложной структуры данных в куче.List.Done заслуживает более подробного рассмотрения. Исключение объекта типа List включает освобождение трех различных типов структур: полиморфных объектов графических фигур в списке, записей Node, которые связывают список вместе и если List расположен в куче, то еще и объект List, который находится в вершине списка. Единственный вызов дестрактора AList порождает целый процесс: AList.Done; Код для дестрактора заслуживает рассмотрения: destructor List.Done; var N : NodePtr; begin while Nodes <> Nil do begin N := Nodes; Dispose(N^.Item, Done); Nodes := N^.Next; Dispose(N); end; end; Список очищается, начиная с вершины с помощью "быстрого" алгоритма, метафорически подобного втягиванию бечевки воздушного змея. Два указателя: указатель Nodes внутри AList и рабочий указатель N изменяют свое содержимое, когда первый элемент списка удаляется. Вызов Dispose освобождает память для первого объекта Point в списке (Item^); тогда Nodes перемещается к следующей записи Node в списке с помощью оператора Nodes := N^.Next; Сама запись Node освобождается и процесс повторяется до тех пор, пока список не закончится. В дестракторе Done следует обратить особое внимание на способ, каким удаляются в списке объекты Point: Dispose(N^.Item, Done); Здесь N^.Item - первый объект Point в списке и вызываемый метод Done представляет собой дестрактор этого объекта. Запомните, что фактический тип N^.Item не обязательно тип Point, он может быть любым типом, порожденным Point. Освобождаемый объект является полиморфным объектом и нельзя сделать никаких предположений о его действительном размере или точном типе во время компиляции. При вызове Dispose, когда Done выполнит все операторы, которые он содержит, "неявный" эпилоговый код Done находит размер освобождаемого экземпляра объекта в таблице виртуальных методов данного экземпляра. Done передает этот размер Dispose, которая затем освобождает в памяти кучи точное количество байтов, которые действительно занимал данный полиморфный объект. Запомните, что полиморфные объекты должны очищаться таким же способом посредством вызова дестрактора, передаваемого Dispose, если необходимо надежно освободить память в куче соответствующего размера. В приведенном примере программы AList объявляется статической переменной в сегменте данных. AList могла бы легко расположить себя в куче и ссылаться на нее можно было бы с помощью указателя типа ListPtr. Если вершина списка была динамическим объектом, то удаление структуры нужно выполнить с помощью вызова дестрактора, выполняемого внутри Dispose: var PList : ListPtr; ... Dispose(PList, Done); Здесь Dispose вызывает метод дестрактора Done, чтобы освободить структуру в куче. Затем, когда Done завершается, Dispose освобождает память, занимаемую содержимым PList, удаляя вершину списка из кучи. LISTDEMO.PAS (на Вашем диске) использует модуль FIGURES.PAS, описанный выше. Она реализует тип Arc как потомок Point, создает объект List, определяет его в качестве вершины связанного списка из трех полиморфных объектов, совместимых с Point, а затем освобождает всю динамическую структуру данных с помощью единственного вызова дестрактора AList.Done. Предыдущая страница | Следующая страница |
|
Web дизайн: Бурлаков Михаил
Web программирование: Бурлаков Михаил