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 приводится пример объектов (динамических и с динамическими полями) и их инициализация с обработкой возможных ошибок.

| 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;

- 291 -

| 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

- 292 -

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