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

Указатель на объект устроен так же, как обычные указатели:

 
 


Описание указателей на объект:

type tpCircle=^tCircle; {тип "указатель на объект типа tCircle"}

var pCircle:tpCircle; {экземпляр этого типа, т.е. указатель на объект

типа tCircle}

pCircle:^tCircle; {другой вариант описания указателя на экземпляр

класса tCircle}

Создание нового объекта в "куче" (heap), на который при создании этого объекта "переключается" указатель pCircle:

New(pCircle);

Независимо от числа созданных объектов, pCircle указывает на последний из таких объектов. Если два раза сделать такой вызов, т.е. New(pCircle), то указатель на предпоследний объект потеряется, как и в случае с обычными динамическими переменными (см. Часть1).

Доступ к полям объекта:

a:=pCircle^.X;

pCircle^.MoveBy(10,21)

и т.п. При этом pCircle читается как "указатель на объект типа tCircle", а pCircle^ — как "объект типа tCircle, на который ссылается указатель pCircle".

Если в объекте есть виртуальные методы, он обязательно должен быть инициализирован конструктором до вызова первого своего метода:

pCircle^.Init(100,100,50);

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

New(pCircle,Init(100,100,50));

Указатель на динамически создаваемый экземпляр можно сразу получить, используя New как функцию. Стоит отметить, что при этом в качестве параметра оператора New ставится не имя указателя, а имя его типа. При этом динамически создается экземпляр указанного типа. Так, если:

type tpArc=^tArc;

var pArc:tpArc;

то можно записать либо

New(pArc);

pArc^.Init(100,100,20,0,90);

либо

pArc:=New(tpArc);

pArc^.Init(100,100,20,0,90);

либо

pArc:=New(tpArc,Init(100,100,20,0,90));

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

Удаление объекта (обычно динамического) производится с помощью вызова специального метода — деструктора. Конструкторов и деструкторов для одного объекта может быть несколько. Если основной конструктор в Turbo Pascal обычно называют Init, то основной деструктор — Done ("сделано"). Деструкторы, в отличие от конструкторов, могут быть виртуальными. Более того, их рекомендуется делать виртуальными, так как в них обычно отсутствуют параметры.

Высвобождение памяти, занимаемой динамическими объектами, производится с помощью оператора Dispose, который имеет обычный для динамических переменных синтаксис:

Dispose(pCircle);

Этот оператор высвобождает память, занимаемую объектом, на который указывает указатель (т.е. на который он "настроен", "переключен"). Надо отметить, что сам указатель pCircle при этом не уничтожается, а продолжает указывать на то же место в памяти. Вызов через pCircle объекта aCircle после того, как соответствующий ему участок памяти высвобожден, недопустим и обычно вызывает ошибку выполнения программы или "зависание" компьютера. Если в объекте есть динамически созданные поля или структуры, их надо убирать в определенной последовательности (иначе либо они могут остаться в памяти как "мусор", либо может произойти попытка повторно убрать уже уничтоженную часть структуры). Поэтому обычно используют специальный метод (в Borland Pascal рекомендуется называть его Done) для удаления динамического объекта. Его желательно делать виртуальным. В этом случае при наличии полиморфизма заранее неизвестно, что удаляется: прародитель или потомок, так как указатель может быть настроен либо на того, либо на другого, и это узнается только во время работы программы. А число полей и, соответственно, занимаемый размер памяти у этих объектов разный. Чтобы правильно производить удаление из памяти полиморфных объектов, метод объявляют деструктором. При этом работа метода замедляется, т.к. происходит проверка, кто вызван — прародитель или потомок. Но зато автоматически высвобождается правильное количество памяти (на основе доступа к механизму позднего связывания при вызове специального варианта Dispose с деструктором):

Dispose(pCircle,Done);

Вызов деструктора без Dispose не приводит к автоматическому высвобождению памяти, и он работает как обычный метод. Надо отметить, что идеология работы с конструкторами и деструкторами в Turbo Pascal является запутанной и непродуманной. Эти недостатки ликвидированы в Delphi.