ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 19

                             ГЛАВА 17.

                    ВНУТРЕННИЙ ФОРМАТ ОБЪЕКТОВ.

     Внутренний формат  объекта  похож  на  запись.  Поля   объекта
запоминаются     в     порядке    объявления,    как    непрерывная
последовательность переменных.  Любые поля,  унаследованные от типа
предка  запоминаются  до  новых  полей,  определенных в порожденном
типе.
     Если объектный тип определяет виртуальные методы, констракторы
или дестракторы,    компилятор  распределяет  дополнительное поле в
объектном типе.  Это 16-ти битовое поле,  называемое полем  таблицы
виртуального метода  (VMT)  и используется для запоминания смещения
VMT этого объектного типа в  сегменте  данных.   Поле  VMT  следует
непосредственно после   обычных  полей  в  объектном  типе.   Когда
объектный тип   наследует  виртуальные  методы,   констракторы  или
дестракторы, он  так же наследует поле VMT, при этом дополнительное
поле не распределяется.
     Инициализация поля VMT экземпляра  производится  констрактором
объектного  типа.  Программа  никогда  явно  не инициализирует и не
обращается к полю VMT.
     Следующие примеры  иллюстрируют  внутренний  формат  объектных
типов.

     type
       LocationPtr = ^Location;
       Location = object
         X, Y: Integer;
         procedure Init(PX, PY: Integer);
         function GetX: Integer;
         function GetY: Integer;
       end;

       PointPtr = ^Point;
       Point    = object(Location)
         Color: Integer;
         constructor Init(PX, PY, PColor: Integer);
         destructor Done; virtual;
         procedure Show; virtual;
         procedure Hide; virtual;
         procedure MoveTo(PX, PY: Integer);
       end;

       CirclePtr = ^Circle;
       Circle = object(Point)
         Radius: Integer;
         constructor Init(PX, PY: Integer;
                          PColor, PRadius: Integer);
         procedure Show; virtual;
         procedure Hide; virtual;
         procedure Fill; virtual;
       end;

     Рис. 17.1   показывает  слои  экземпляров  Location,  Point  и
Circle; каждый прямоугольник соответствует одному слову памяти.

       Рис. 17.1 Слои экземпляров Location, Point и Circle.


           Location           Point            Circle
         ЪДДДДДДДДДДї     ЪДДДДДДДДДДДї     ЪДДДДДДДДДДДї
         і X        і     і X         і     і X         і
         ГДДДДДДДДДДґ     ГДДДДДДДДДДДґ     ГДДДДДДДДДДДґ
         і Y        і     і Y         і     і Y         і
         АДДДДДДДДДДЩ     ГДДДДДДДДДДДґ     ГДДДДДДДДДДДґ
                          і Color     і     і Color     і
                          ГДДДДДДДДДДДґ     ГДДДДДДДДДДДґ
                          і VMT       і     і VMT       і
                          АДДДДДДДДДДДЩ     ГДДДДДДДДДДДґ
                                            і Radius    і
                                            АДДДДДДДДДДДЩ


     Поскольку Point является  первым  типом  в  иерархии,  который
вводит  виртуальные  методы,  поле  VMT  распределяется  после поля
Color.

                     Таблицы виртуальных методов.

     Каждый объектный  тип,    который   содержит   или   наследует
виртуальные методы,     констракторы    или   дестракторы,    имеет
соответствующую ему VMT,   которая  хранится  в  инициализированной
части сегмента  данных  программы.   Существует только одна VMT для
объектного типа (а не для экземпляра),  но два различных  объектных
типа никогда  не  разделяют  VMT,   как бы похожи они не были.  VMT
строится компилятором   автоматически   и    никогда    прямо    не
обрабатывается программой.    Кроме   того,    указатели   на   VMT
автоматически запоминаются    в    экземпляре    объектного    типа
констрактором этого  объектного  типа  и  никогда не обрабатываются
программой.
     Первое слово VMT содержит размер экземпляров,  ассоциированных
с объектным типом; эта  информация  используется  констракторами  и
дестракторами для  определения  сколько байт нужно распределить или
освободить, используя расширенный синтаксис New и Dispose.
     Второе слово  VMT  содержит  отрицательный размер экземпляров,
связанных с объектным типом; эта информация используется механизмом
верификации вызовов       виртуальных   методов   для   определения
неинициализированных объектов (экземпляров,  для  которых  не  было
вызова констрактора) и для проверки целостности VMT. Когда механизм
верификации виртуальных  вызовов   включен   (используя   директиву
компилятора   {$R+},   которая  расширена  для  включения  проверки
виртуальных  методов),  компилятор   генерирует   вызов   программы
верификации   VMT   перед  каждым  виртуальным  вызовом.  Программа
верификации VMT проверяет,  что первое слово VMT не равно 0  и  что
сумма  первого  и  второго  слов = 0.  Если какая-то из проверок не
проходит, генерируется ошибка времени выполнения 210.

     Примечание: Включение  проверки  на  диапазон  и   верификации
вызовов  виртуальных  методов  замедляет Вашу программу и делает ее
значительно больше,  поэтому используйте состояние {$R+} только  во
время  отладки  и  переходите  в  состояние {$R-} для окончательной
версии программы.

     И наконец,   начиная со смещения 4 в VMT находится список 32-х
битных указателей на метод,   по  одному  на  виртуальный  метод  в
объектном типе,   в порядке объявления.  Каждое поле содержит адрес
точки входа соответствующего виртуального метода.
     Рис.17.2 показывает слои VMT для типов  Point  и  Circle  (Тип
Location  не  имеет  VMT  так  как не содержит виртуальных методов,
констракторов  и  дестракторов);  каждый  маленький   прямоугольник
соответствует одному слову памяти, а каждый большой - двум.

               Рис.17.2 Слои VMT для Point и Circle.

              Point VMT                 Circle VMT
          ЪДДДДДДДДДДДДДДДї         ЪДДДДДДДДДДДДДДДДї
          і $0008         і         і $000A          і
          ГДДДДДДДДДДДДДДДґ         ГДДДДДДДДДДДДДДДДґ
          і $FFF8         і         і $FFF6          і
          ГДДДДДДДДДДДДДДДґ         ГДДДДДДДДДДДДДДДДґ
          і @Point.Done   і         і @Point.Done    і
          ГДДДДДДДДДДДДДДДґ         ГДДДДДДДДДДДДДДДДґ
          і @Point.Show   і         і @Circle.Show   і
          ГДДДДДДДДДДДДДДДґ         ГДДДДДДДДДДДДДДДДґ
          і @Point.Hide   і         і @Circle.Hide   і
          ГДДДДДДДДДДДДДДДґ         ГДДДДДДДДДДДДДДДДґ
          і @Point.MoveTo і         і @Point.MoveTo  і
          АДДДДДДДДДДДДДДДЩ         ГДДДДДДДДДДДДДДДДґ
                                    і @Circle.Fill   і
                                    АДДДДДДДДДДДДДДДДЩ


     Заметьте, как Circle наследует методы MoveTo и Down из Point и
как он перекрывает методы Show и Hide.
     Как упомянуто  ранее,  констрактор  объектного  типа  содержит
специальный код,  который сохраняет смещение VMT объектного типа  в
инициализируемом экземпляре.  Например:  пусть дан экземпляр Р типа
Point  и  экземпляр  C  типа  Circle,  тогда  вызов  P.Init   будет
автоматически   сохранять   смещение  VMT  для  Point  в  поле  VMT
экземпляра P и вызов C.Init будет сохранять смещение VMT для Circle
в поле VMT экземпляра C.  Эта автоматическая инициализация является
частью входного кода констрактора так,  что  в  тот  момент,  когда
управление  достигает  оператора begin констрактора,  поле Self VMT
уже установлено.  Поэтому, если возникла необходимость, констрактор
может сделать вызов виртуального метода.


                    Стандартная функция SizeOf.

     Когда стандартная  функция  SizeOf  применяется  к  экземпляру
объектного типа,  имеющего VMT, она возвращает размер, хранящийся в
этой VMT.  Поэтому, для объектных типов, имеющих VMT, SizeOf всегда
возвращает не объявленный, а действительный размер экземпляра.


                    Стандартная функция TypeOf.

     Turbo Pascal  добавляет  новую  стандартную  функцию   TypeOf,
которая  возвращает указатель на VMT объектного типа.  TypeOf имеет
единственный параметр,  который  может  быть  либо  идентификатором
объектного типа,  либо экземпляром объектного типа. В обоих случаях
результат типа  Pointer является указателем на VMT объектного типа.
TypeOf может быть применен только к объектным типам,  имеющим VMT -
для всех других типов это будет ошибкой.
     Функция TypeOf    может   быть   использована   для   проверки
действительного типа экземпляра. Например:

     if TypeOf(Self) = ТypeOf(Point) then ...


                    Вызовы виртуальных методов.

     Чтобы вызвать  виртуальный  метод,  компилятор генерирует код,
который указывает на адрес VMT из  поля  VMT  в  объекте,  и  затем
вызывает  через  поле,  связанное  с  этим методом.  Например:  для
переменной PP типа PointPtr  вызов  PP^.Show  генерирует  следующий
код:

     les    di,PP              ; Загрузить PP в ES:DI
     push   es                 ; передать как Self параметр
     push   di
     mov    di,es:[di + 6]     ; отметить смещение VMT из поля VMT
     call   DWORD PTR [di+8]   ; вызвать точку входа в VMT для Show


     Правило совместимости   для   объектных   типов  позволяет  PP
указывать на Point или Circle, или на любой другой тип, порожденный
от Point.  И если Вы проверите VMT,  показанную здесь,  Вы увидите,
что для Point точка в VMT со смещением 8 указывает на Point.Show, в
то  время,  как  для  Circle  она  указывает на Circle.Show.  Таким
образом, в зависимости от актуального (времени выполнения) типа PP,
инструкция CALL вызывает Point.Show или Circle.Show, или метод Show
любого другого типа, порожденного от Point.
     Если Show  -  статический метод,  то для вызова PP^.Show будет
генерироваться код:

     les      di,PP               ; Загрузить PP в ES:DI
     push     es                  ; передать как Self параметр
     push     di
     call     Point.Show          ; прямой вызов Point.Show

     Здесь вне зависимости от того,  на что указывает PP, код будет
всегда будет вызывать метод Point.Show.


                   Соглашение о вызовах методов.

     Методы используют такие же соглашения о вызовах,  как  обычные
процедуры и  функции,  за исключением того,  что каждый метод имеет
дополнительный неявный   параметр,    называемый   Self,    который
соответствует var  -  параметру  того  же  типа,  как объектный тип
метода. Параметр Self всегда передается как  последний  параметр  и
всегда имеет  форму  32-х  битового  указателя на экземпляр,  через
который метод  вызывается.   Например,   для  переменной  PP   типа
PointPtr, определенный ранее, вызов PP^.MoveTo(10, 20) кодируется:

     Mov     ax,10             ; загрузить 10 в AX
     Push    ax                ; передать РX как параметр
     Mov     ax,20             ; загрузить 20 в AX
     Push    ax                ; передать РY как параметр
     les     di, pp            ; Загрузить PP в ES:DI
     push    es                ; передать как Self параметр
     push    di
     mov     di,es:[di+6]      ; отметить смещение VMT из поля VMT
     call    DWORD PTR [di+16] ; вызвать точку входа в VMT для
                               ; MoveTo

     До возврата  метод  должен  удалить параметр Self из стека так
же, как он должен удалить любые обычные параметры.
     Метод всегда  использует  дальнюю модель вызова (FAR CALL) вне
зависимости от установления директивы компилятора $F.


                    Констракторы и дестракторы.

     Констракторы и  дестракторы  используют  те  же  соглашения  о
вызовах, что  и  обычные  методы,   за   исключением   того,    что
дополнительный параметр,     размером    в    слово,     называемый
VMT-параметром, передается в стек перед параметром Self.
     Для констракторов   параметр   VMT   содержит   смещение  VMT,
принадлежащее Self, для того, чтобы инициализировать этот Self.
     Когда констрактор  вызывается  для  распределения динамических
объектов, используя  расширенный  синтаксис  New,   указатель   nil
передается в    параметре   Self.    Это   заставляет   констрактор
распределять новый динамический объект,  адрес которого  передается
назад в  DX:AX  при  возврате из констрактора.  Если констрактор не
может распределить объект,  указатель nil возвращается в DX:AX (см.
"Восстановление ошибок констрактора" в следующем разделе).
     Наконец, когда     констрактор     вызывается,       используя
уточненный идентификатор   метода  (т.е.  идентификатор  объектного
типа), с последующей точкой и идентификатором метода, значение ноль
передается в параметре VMT.  Это указывает констрактору,  что он не
должен инициализировать поле VMT для Self.
     Для дестрактора  ноль  в  параметре VMT указывает о нормальном
вызове, и ненулевое значение указывает,  что дестрактор был вызван,
используя расширенный синтаксис Dispose.  Это заставляет дестрактор
освобождать Self сразу перед возвратом  (размер  Self  определяется
просмотром первого слова VMT для этого Self).


                   Расширения для New и Dispose.

     Стандартные процедуры   New   и    Dispose    расширены    для
использования  вызова  констрактора  или  дестрактора  как  второго
параметра,  для   распределения   или   освобождения   динамических
переменных объектного типа. Синтаксис:

     New(P, Construct);

     и

     Dispose(P, Destruct);

     где P - переменная указатель,  указывающий на объектный тип, а
Construct и Destruct вызывают констракторы и дестракторы  для этого
объектного типа.   Для   New   действие   расширенного   синтаксиса
равносильно выполнению

     New(P);
     P^.Construct;

     и для Dispose

     P^.Destruct;
     Dispose(P);

     Без расширенного синтаксиса появление таких пар вызова  New  с
последующим вызовом констрактора и вызова дестрактора с последующим
вызовом  Dispose  будут  слишком  частыми.  Расширенный   синтаксис
улучшает  читабельность,  а  так  же  генерирует  более  короткий и
эффективный код.  Следующее иллюстрирует использование расширенного
синтаксиса New и Dispose.

     var
        SP: StrFieldPtr;
        ZP: ZipFieldPtr;
     begin
        New(SP, Init(1, 1, 25, 'FirstName'));
        New(ZP, Init(1, 2, 5, 'Zip Code', 0, 99999));
        SP^.Edit;
        ZP^.Edit;
        ...
        Dispose(ZP, Down);
        Dispose(SP, Down);
     end;

     Дополнительное расширение  позволяет  использовать   New   как
функцию,  которая  распределят и возвращает динамическую переменную
указанного типа. Синтаксис :

     New(T);

     или

     New(T, Construct);

     В первой форме Т может быть указателем на любой тип. Во второй
-  Т  должно  указывать  на  объектный  тип и Construct должен быть
вызван для создания этого объектного типа.  В обоих случаях функция
возвращает результат Т.

     Пример :

     var
        F1, F2: FieldPtr;
     begin
        F1 := New(StrFieldPtr, Init(1, 1, 25, 'FirstName'));
        F2 := New(ZipFieldPtr, Init(1, 2, 5, 'Zip Code', 0, 99999));
        ...
        Writeln(F1^.GetStr);  {вызов StrField.GetStr}
        Writeln(F2^.GetStr);  {вызов ZipField.GetStr}
        ...
        Dispose(F2, Down);    {вызов Field.Down}
        Dispose(F1, Down);    {вызов StrField.Down}
     end;

     Заметим, что даже,  хотя F1 и F2  типа  FieldPtr,  расширенные
правила  совместимости по присваиванию указателей позволяют F1 и F2
присваивать  указатели  на  любой  порожденный  тип  от  Field;   и
поскольку  GetStr  и Down являются виртуальными методами,  механизм
диспетчеризации  виртуальных  методов  будет   корректно   вызывать
StrField.GetStr, ZipField.GetStr,    Field.Down   и   StrField.Down
соответственно.


                       Методы на ассемблере.

     Методы, реализованные  на языке ассемблера могут быть включены
в программу на Turbo Pascal,  используя директиву компилятора $L  и
ключевое  слово  External.  Объявление  внешнего метода в объектном
типе не отличается от обычного метода;  однако,  реализация  метода
состоит  только из заголовка метода с последующим зарезервированным
словом External.
     В исходном   тексте   на  ассемблере,  @  используется  вместо
точки при написании уточненного идентификатора  (т.к.  точка  имеет
другое   значение   в   ассемблере   и   не   может   быть   частью
идентификатора).  Например, идентификатор Паскаля Rect.Init пишется
в  ассемблере  как Rect@Init.  Синтаксис с @ может быть использован
для объявления и PUBLIC и EXTRN идентификаторов.
     В качестве  примера  метода на ассемблере мы реализуем простой
объект Rect.

     Type
        Rect = Object
          X1, X2, Y1, Y2: Integer;
          procedure Init(XA, YA, XB, YB: Integer);
          procedure Union(var R: Rect);
          function Contains(X, Y: Integer): Boolean;
        end;

     Rect представляет   прямоугольник,    ограниченный    четырьмя
координатами X1,   Y1,  X2,  Y2.  Верхний левый угол прямоугольника
определяется X1,  Y1  и  нижний  правый  -  X2,   Y2.   Метод  Init
присваивает значение   координатам   прямоугольника;   метод  Union
вычисляет наименьший  прямоугольник,   который  содержит   и   этот
прямоугольник, и  другой  прямоугольник;  метод Contains возвращает
True, если данная точка лежит внутри прямоугольника, и False - если
нет. Другие   методы,   такие  как  перемещение,   масштабирование,
вычисление пересечений и тестирование на равенство могут быть легко
реализованы, чтобы сделать Rect более полезным объектом.
     Реализация методов для  Rect  на  Паскале  только  перечисляет
заголовки этих методов с ключевым словом External.

     {$L Rect}
     procedure Rect.Init(XA, YA, XB, YB : Integer); External;
     procedure Rect.Union(var R : Rect); External;
     function Rect.Contains(X, Y : Integer) : Boolean; External;

     Разумеется, нет никаких  требований,  чтобы  все  методы  были
реализованы как внешние.  Любой метод может быть реализован либо на
Паскале, либо на Ассемблере, по необходимости.
     Исходный текст   программы  на  ассемблере  Rect.Asm,  который
реализует эти три внешних метода:

     TITLE   Rect
     LOCALS  @@

     ; Rect Structure

     Rect     STRUC
     X1       DW        ?
     Y1       DW        ?
     X2       DW        ?
     Y2       DW        ?
     Rect     ENDS

     code     SEGMENT   BYTE PUBLIC

              ASSUME    cs:code

     ; procedure Rect.Init(XA, YA, XB, YB: Integer)

              PUBLIC    Rect@Init

     Rect@Init          PROC       FAR

     @XA                EQU        (WORD PTR[bp+16])
     @YA                EQU        (WORD PTR[bp+14])
     @XB                EQU        (WORD PTR[bp+12])
     @YB                EQU        (WORD PTR[bp+10])
     @Self              EQU        (DWORD PTR[bp+6])

              Push      bp         ; сохранить bp
              Mov       bp,sp      ; установить размер стека
              les       di,@Self   ; загрузить Self в ES:DI
              cld                  ; сдвинуть вперед
              mov       ax,@XA     ; X1 := XA
              stosw
              mov       ax,@YA     ; Y1 := YA
              stosw
              mov       ax,@XB     ; X2 := XB
              stosw
              mov       ax,@YB     ; Y2 := YB
              stosw
              pop       bp         ; восстановить bp
              ret       12         ; выгрузить параметры и возврат

     Rect@Init          ENDP

     ; procedure Rect.Union(var R: Rect)

              PUBLIC    Rect@Union

     Rect@Union         PROC       FAR

     @R                 EQU        (DWORD PTR [bp+10])
     @Sef               EQU        (DWORD PTR [bp+6])

              push      bp         ; сохранить bp
              mov       bp,sp      ; установить размер стека
              push      ds         ; сохранить ds
              lds       si,@R      ; загрузить R в DS:SI
              les       di,@Self   ; загрузить Self в DS:SI
              cld                  ; передвинуть вперед
              lodsw                ; if R.X1 >= X1 goto @@1
              scasw
              jge       @@1
              dec       di         ; X1 := R.X1
              dec       di
              stosw
     @@1:     lodsw                ; if R.Y1 >= Y1 goto @@2
              scasw
              jge       @@2
              dec       di         ; Y1 := R.Y1
              dec       di
              stosw
     @@2:     lodsw                ; if R.X2 <= X2 goto @@3
              scasw
              jle       @@3
              dec       di         ; X2 := R.X2
              dec       di
              stosw
     @@3:     lodsw                ; if R.Y2 <= Y2 goto @@4
              scasw
              jle       @@4
              dec       di         ; Y2 := R.Y2
              dec       di
              stosw
     @@4      pop       ds         ; восстановить ds
              pop       bp         ; восстановить bp
              ret       8          ; выгрузить параметры и возврат

     Rect@Union         ENDP

     ; function Rect.Contains(X, Y: Integer): Boolean

              PUBLIC    Rect@Contains

     Rect@Contains      PROC        FAR

     @X                 EQU         (WORD PTR[BP+12])
     @Y                 EQU         (WORD PTR[BP+10])
     @Self              EQU         (DWORD PTR[BP+6])

               push     bp          ; сохранить bp
               mov      bp,sp       ; установить размер стека
               les      di,@Self    ; загрузить Self в ES:DI
               mov      al,0        ; возвращает false
               mov      dx,@X       ; if (XX2) goto @@1
               cmp      dx,es:[di].X1
               jl       @@1
               cmp      dx,es:[di].X2
               jg       @@1
               mov      dx,@Y       ; if (YY2) goto @@2
               cmp      dx,es:[di].Y1
               jl       @@1
               cmp      dx,es:[di].Y2
               jg       @@1
               inc      ax          ; возвращает true
     @@1:      pop      bp          ; восстановить bp
               ret      8           ; выгрузить параметры и возврат

     Rect@Contains      ENDP

     code      ENDS

               END



                Восстановление ошибок констрактора.

     Как описано  в  главе   16,   Turbo   Pascal   позволяет   Вам
устанавливать   функции  обработки  ошибок  кучи  через  переменную
HeapError,   объявленную   в   модуле   System.   Эта   возможность
поддерживается так  же  в Turbo Pascal,  но дополнительно позволяет
воздействовать на работу констракторов объектных типов.
     По умолчанию,   когда  нет достаточно памяти для распределения
динамического экземпляра   объектного  типа,   вызов  констрактора,
использующий расширенный     синтаксис   New,    генерирует  ошибку
времени выполнения 203.  Если Вы устанавливаете  функцию  обработки
ошибок  кучи,  которая  возвращает  1  (в  отличие  от стандартного
результата  функции  -  0),  вызов  констрактора  через  New  будет
возвращать  nil (вместо аварийного завершения программы) если он не
может полностью обработать запрос на выделение памяти.
     Код, который производит распределение и инициализацию поля VMT
динамического экземпляра является частью входной последовательности
констрактора: когда    управление    достигает    оператора   begin
констрактора, экземпляр уже распределен  и  инициализирован.   Если
распределение неуспешно  и функция обработки ошибок кучи возвратила
1, констрактор пропускает выполнение операторной части и возвращает
указатель nil;  указатель,   заданный  в процедуре New,  вызывающей
констрактор, установится в nil.

     Примечание: Введена новая стандартная процедура Fail.

     Когда управление  достигает  оператора   begin   констрактора,
гарантируется, что  экземпляр  объектного  типа  был  распределен и
инициализирован успешно.  Однако констрактор сам может распределять
динамические переменные  для  того,   чтобы  инициализировать  поля
указателей в  экземпляре  и  эти  распределения  могут  завершиться
неуспешно. Если  это случится,  правильно разработанный констрактор
должен сделать  "откат"  всех  успешных  распределений  и  в  конце
освободить экземпляр   объектного  типа  так,   чтобы  возвращаемый
результат получил  указатель  nil.   Чтобы  сделать   такой   откат
возможным, Turbo  Pascal  реализует  новую  стандартную  процедуру,
называемую Fail,  которая не имеет параметров и которая может  быть
вызвана  только из констрактора.  Вызов Fail заставляет констрактор
освобождать динамический экземпляр,  который  был  распределен  при
входе  в  констрактор  и  при  этом  возвращать  указатель  nil для
индикации ошибки.
     Когда динамический  экземпляр  распределен  через  расширенный
синтаксис New,  значение nil  в  заданном  указателе  говорит,  что
операция закончилась с ошибкой. К сожалению нет такого указателя, с
помощью  которого  можно  было  бы  проверить   операцию   создания
статического  экземпляра,  или  вызов унаследованного констрактора.
Вместо этого Turbo  Pascal  позволяет  использовать  констрактор  в
выражении как булевскую функцию:  возврат True говорит об успехе, а
возврат False говорит  об  ошибке  (благодаря  вызову  Fail  внутри
констрактора).
     Следующая программа  реализует  два  простых  объектных  типа,
содержащих   указатели.   Первая   версия  программы  не  реализует
восстановление ошибок констрактора.

     type
        LinePtr = ^Line;
        Line = String[79];
        BasePtr = ^Base;
        Base = Object
          L1, L2: LinePtr;
          Constructor Init(S1, S2: Line);
          Destructor Done; virtual;
          procedure Dump; virtual;
        end;

        DerivedPtr = ^Derived;
        Derived = object(Base)
          L3, L4: LinePtr;
          Constructor Init(S1, S2, S3, S4: Line);
          Destructor Done; virtual;
          procedure Dump; virtual;
        end;

     var
        BP: BasePtr;
        DP: DerivedPtr;

     Constructor Base.Init(S1, S2: Line);
     begin
        New(L1);
        New(L2);
        L1^ := S1;
        L2^ := S2;
     end;

     Destructor Base.Done;
     begin
        Dispose(L2);
        Dispose(L1);
     end;

     procedure Base.Dump;
     begin
        Writeln('B: ', L1^, ',', L2^, '.');
     end;

     Constructor Derived.Init(S1, S2, S3, S4: Line);
     begin
        Base.Init(S1, S2);
        New(L3);
        New(L4);
        L3^ := S3;
        L4^ := S4;
     end;

     Destructor Derived.Done;
     begin
       Dispose(L4);
       Dispose(L3);
       Base.Done;
     end;

     procedure Derived.Dump;
     begin
        Writeln('D: ', L1^, ',', L2^, ',', L3^, ',', L4^, '.');
     end;

     begin
        New(BP, Init('Turbo', 'Pascal'));
        New(DP, Init('North', 'East', 'South', 'West'));
        BP^.Dump;
        DP^.Dump;
        Dispose(DP, Done);
        Dispose(BP, Done);
     end.


         Следующий пример  демонстрирует  как предыдущий может быть
переписан с использование восстановления ошибок. Объявления типов и
переменных не повторяются, т.к. они остаются такими же.


     Constructor Base.Init(S1, S2: Line);
     begin
        New(L1);
        New(L2);
        if (L1 = nil) or (L2 = nil) then
        begin
           Base.Done;
           Fail;
        end;
        L1^ := S1;
        L2^ := S2;
     end;

     Destructor Base.Done;
     begin
        if L2 <> nil then Dispose(L2);
        if L1 <> nil then Dispose(L1);
     end;

     Constructor Derived.Init(S1, S2, S3, S4: Line);
     begin
        if not Base.Init(S1, S2) then Fail;
        New(L3);
        New(L4);
        if (L3 = nil) or (L4 = nil) then
        begin
           Derived.Done;
           Fail;
        end;
        L3^ := S3;
        L4^ := S4;
     end;

     Destructor Derived.Done;
     begin
       if L4 <> nil then Dispose(L4);
       if L3 <> nil then Dispose(L3);
       Base.Done;
     end;

     {$F+}
     function HeapFunc(Size: Word): Integer;
     begin
        HeapFunc := 1;
     end;
     {$F-}

     begin
        HeapError := @HeapFunc;  {установка управления ошибками
                                  кучи}
        New(BP, Init('Turbo', 'Pascal'));
        New(DP, Init('North', 'East', 'South', 'West'));
        if (BP = nil) or (DP = nil) then
          Writeln('Oшибка распределения')
        else
        begin
          BP^.Dump;
          DP^.Dump;
        end;
        if DP <> nil then Dispose(DP, Done);
        if BP <> nil then Dispose(BP, Done);
     end.

     Заметьте как  соответствующие  дестракторы   в   Base.Init   и
Derived.Init используются    для   отката   после   всех   успешных
распределений перед вызовом Fail.  Заметьте так же,  как вызывается
Base.Init  в Derived.Init для того,  чтобы проверить успешный вызов
унаследованного констрактора.


Яндекс цитирования