Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
TURBO_PASCAL.doc
Скачиваний:
8
Добавлен:
01.07.2025
Размер:
8 Мб
Скачать
☆

13.6.2. Освобождение объектов. Деструкторы

Для освобождения кучи от динамических объектов применяется стандартная процедура

Dispose( ИмяСсылкиНаОбъект ).

Подобный вызов уничтожит объект в целом. Но если поля данных объекта были динамическими и под них выделялась дополнительная память при выполнении конструктора или иной процедуры инициализации, то их надо освободить до уничтожения самого объекта. Для этих целей (или других завершающих действий) вводится специальный вид метода — деструктор. Он объявляется среди прочих методов служебным словом DESTRUCTOR вместо PROCEDURE (пример реализации деструктора приведен в разд. 13.6.3). По сути, деструктор — это метод, противоположный конструктору. Обычно, если конструктор рекомендуется называть Init, то деструктору дается имя Done («завершено»). Для одного типа объекта могут быть определены несколько деструкторов. Деструкторы могут наследоваться. Они могут быть статическими или виртуальными, но лучше использовать виртуальные, так как это гарантирует, что будет выполнен именно тот деструктор, который соответствует данному типу объекта. Объект может иметь деструктор даже в том случае, когда все его методы — статические. Смысл и необходимость введения деструктора заключаются в том, что его можно использовать в расширенной процедуре Dispose так же, как используется конструктор в New.

Расширенный синтаксис процедуры Dispose позволяет в качестве второго параметра передавать имя деструктора данного типа объекта:

Dispose( ИмяСсылкиНаОбъект, ИмяДеструктора );

Действует этот вызов следующим образом. Сначала выполняется вызов деструктора (описанные в нем завершающие действия) как обычного метода. Далее, если объект содержит виртуальные методы, то деструктор осуществляет поиск размера объекта в таблице виртуальных методов и передает размер процедуре Dispose, которая освобождает правильное количество байтов памяти. Поэтому для динамических объектов всегда имеет смысл объявлять виртуальный деструктор, хотя бы и пустой:

DESTRUCTOR ИмяТипаОбъекта.Done; VIRTUAL;

BEGIN END;

который нужен для нормальной работы процедуры Dispose. {288}

13.6.3. Обработка ошибок при работе с динамическими объектами

Если при попытке разместить динамический экземпляр типа «объект» свободной памяти окажется недостаточно, то вызов расширенной процедуры New сгенерирует код ошибки выполнения 203. Но если переписать системную функцию HeapFunc (см. разд. 11.5.6) таким образом, чтобы она возвращала значение 1 вместо 0, то в размещаемую ссылочную переменную в случае ошибки вернется значение nil и программа не прервется. Если при запросе памяти для объекта процедурой

New( ИмяСсылкиНаОбъект, ИмяКонструктора )

функция HeapFunc выдаст значение 1, то конструктор не будет выполняться, а в ИмяСсылкиНаОбъект запишется nil.

Когда начинает выполняться тело конструктора, экземпляр объекта уже будет гарантированно и успешно распределен. Однако сам конструктор может выполнять действия по распределению динамических полей данных экземпляра, и при распределении таких полей может произойти сбой, если не хватит памяти. Будет разумно, если в подобной ситуации конструктор отменит все уже проделанные распределения и в завершение освободит экземпляр типа объекта так, чтобы в результате ссылка получила бы значение nil. Для этого введена стандартная процедура Fail, не имеющая параметров. Она может быть вызвана только из конструктора. Вызов этой процедуры освобождает динамический экземпляр, который был размещен в памяти до входа в конструктор, и возвращает в ссылке значение nil. Получение nil обозначает неудачу распределения памяти.

Нехватка памяти возможна и в случае статических объектов с динамическими полями. Так, при размещении конструктором динамических полей в куче может возникнуть нехватка памяти. Но, поскольку объект статический, нельзя передать сигнальное значение nil в ссылку — ее попросту нет. Вместо этого предлагается использовать имя конструктора как логическую функцию. Если внутри конструктора была вызвана процедура Fail, то в имени конструктора вернется значение False. В остальных случаях будет возвращаться значение True. Подобным способом анализа можно пользоваться и для проверки работы унаследованных конструкторов.

На рис. 13.8 приводится пример объектов (динамических и с динамическими полями) и их инициализация с обработкой возможных ошибок. {289}

TYPE

VectorType = Array [ 1..1000 ] of Real; { вектор }

MatrixType = Array [1..20,1..20 ] of Real; { матрица }

VectorTypePtr = ^VectorType; { ссылка на вектор }

MatrixTypePtr = ^MatrixType; { ссылка на матрицу }

Vector = OBJECT {статический объект с динамическим полем }

V : VectorTypePtr;

CONSTRUCTOR Init( FillVect : Real );

DESTRUCTOR Done; VIRTUAL;

PROCEDURE Work; VIRTUAL;

END;

ComplexPtr = ^Complex; { Динамический объект с }

Complex = OBJECT(Vector) { динамическими полями данных }

M : MatrixTypePtr,

CONSTRUCTOR Init(FillVect, FillMat : Real);

DESTRUCTOR Done;

VIRTUAL;

PROCEDURE Work; VIRTUAL;

END;

{ Реализация методов объектов }

CONSTRUCTOR Vector.Init( FillVect : Real );

VAR i : Word; { параметр цикла заполнения }

BEGIN

New( V ); { попытка разместить V в куче }

if V=nil then begin { при неудаче сделать "откат" }

Vector.Done; { завершение работы объекта }

Fail { объявить сбой конструктора }

end; {if}

for i:=1 to 1000 do { поле V создано и заполняется }

V^[i] := FillVect;

END;

DESTRUCTOR Vector.Done;

BEGIN

if V<>nil then Dispose( V ) { освобождение поля V }

END;

PROCEDURE Vector.Work; { метод обработки поля V }

BEGIN

{ Какие-либо действия над вектором V^ }

END; {290}

CONSTRUCTOR Convex.Init( FillVect, FillMat : Real );

VAR i,j : Word; { параметры циклов заполнения}

BEGIN

if not Vector.Init(FillVect) {инициализация прародителя }

then Fail; {при неудаче сделать откат }

New( M ); { попытка разместить M в куче }

if M=nil then begin { при неудаче сделать "откат" }

Complex.Done; { завершение работы объекта }

Fail { объявить сбой конструктора }

end; {if}

for i:=1 to 20 do {поле М создано и заполняется }

for j:=1 to 20 do

M^[i,j] := FillMat;

END;

DESTRUCTOR Complex.Done;

BEGIN

if M<>nil then Dispose( M ); { освобождение поля M } Vector.Done { освобождение поля V }

END;

PROCEDURE Complex.Work; { метод обработки поля М }

BEGIN

{ Какие-либо действия над матрицей M^ }

END;

{$F+} { новая функция анализа распределения памяти}

FUNCTION HeapFunc(Size: Word) : Integer;

BEGIN

HeapFunc := 1

END;

{$F-}

VAR

Vec : Vector; { экземпляр статического объекта}

ComPtr : ComplexPtr; { ссылка на динамический объект }

BEGIN

HeapError := ©HeapFunc; { подстановка функции анализа }

if not Vec.Init( 1.0 ) { инициализация поля Vec.V }

then Halt( 1 ); { реакция на неудачу операции }

New(ComPtr, Init(2,3)); { размещение объекта ComPtr^ }

if ComPtr=nil { реакция на неудачу операции }

then Halt( 2 );

(* Применение методов Vec.Work и ComPtr^.Work *)

Vec.Done; { освобождение поля Vec.V }

Dispose(ComPtr, Done); { освобождение объекта ComPtr^ }

END.

Рис. 13.8{291}

Обращаем внимание на вызовы деструкторов в Vector.Init и Complex.Init перед вызовом процедуры Fail. Они нужны для отмены всех успешных размещений полей. Также важно то, что в Complex.Init вызов Vector.Init записан в выражении таким образом, что можно проверить успешность выполнения конструктора прародителя.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]