Жизненный цикл объекта

Материал из wikiru.visual-prolog.com

Версия от 11:17, 20 декабря 2007; SergeMukhin (обсуждение | вклад) (→‎Финализатор)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)

Создание объекта

Других способов создания объектов (экземпляров класса), кроме как с использованием предикатов, объявленных для данного класса конструктором (явно или не явно), нет. Если конструктор не объявлен, то может быть использован предопределенный конструктор new().

Гибель объекта

VIP не содержит никаких средств для уничтожения объектов. Тем не менее они не живут вечно (до конца жизни приложения). Время жизни объектов определяется сборщиком мусора (GarbageCollector), встроенного в RunTime часть VIP. Сборщик мусора считает, что память, занимаемая объектом (экземпляром), может быть освобождена, когда исчезнут все ссылки на объект. И это почти единственное правило, определяющее время жизни объекта (экземпляра). Исходя из этого правила объект, созданный в пределах одного клоза, если ссылка на него не сохранена, погибает по завершении клоза. Например,

clauses
  мойПример():-
     МойОбъект=мойОбъект::new(),
     МойОбъект:мойПредикатИзМойОбъект(),
     ...
     последнийПредикатКлоза().

Здесь указатель на экземпляр класса мойОбъект сохраняется в переменной МойОбъект. Поэтому, пока клоз мойПример выполняется, живет и доступен через указатель МойОбъект и экземпляр мойОбъект. По правилам Пролога, переменные в клозах являются локальными и сохраняют свои значения только в пределах клоза. Следовательно, по завершении выполнения клоза мойПример объектмойОбъект считается недоступным и может умереть. Если, конечно, он не породил ссылок на себя самого в какой-нибудь постоянной памяти во время вызова конструктора или других своих предикатов. Случай

clauses
  мойПример():-
     МойОбъект=мойОбъект::new(),
     МойОбъект:мойПредикатИзМойОбъект(...),
     ...
     вызовПредикатаДругогоКлоза(МойОбъект),    
     ...
     последнийПредикатКлоза(...).

не меняет сути - пока выполняется предикат вызовПредикатаДругогоКлоза(МойОбъект) экземпляр продолжает жить и умрет после завершения выполнения предиката последнийПредикатКлоза(...).

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

Для этой цели используется хранение указателей на объекты в фактах. В VIP как предикаты, так и факты типизированы. А это значит, что при декларировании факта (а равно и предиката) необходимо указание домена - либо предопределённого в языке, либо объявленного в программе. Применительно к указателям на объекты, специального объявления домена, связанного с объектами, не существует. Здесь действует правило: объявление интерфейса класса является одновременно и объявлением домена, определяющего этот класс.

Поэтому имя интерфейса используется в качестве домена при декларировании фактов для хранения указателей или их передачи в качестве параметров. Например,

class мойКласс:мойИнтерфейс
end class мойКласс

уже определяет то обстоятельство, что все экземпляры класса мойКласс принадлежат домену мойИнтерфейс. Поэтому объявление факта либо в виде

facts
мойУказатель_F:(string УдобноеИмяЭкземпляра,мойИнтерфейс УказательНаЭкземпляр).

либо в виде

facts
мойУказатель_V:мойИнтерфейс:=erroneous.

Являются правильными (не путать с целесообразностью) объявлениями. Тогда в примере

clauses
  мойПример():-
     МойОбъект=мойОбъект::new(),
     МойОбъект:мойПредикатИзМойОбъект(...),
     мойУказатель_V:=МойОбъект,
     ...
     вызовПредикатаДругогоКлоза(МойОбъект),    
     ...
     последнийПредикатКлоза(...).

время жизни объекта продлевается за пределы времени жизни клоза. Объект будет существовать все то время, пока на него существует указатель. Когда объект больше не нужен, достаточно удалить ссылку на него, например так:

     ...
     мойУказатель_V:=erroneous,
     ...

или так

     ...
     retract(мойУказатель_F("ПочтовыеАдреса",_УказательНаЭкземпляр)),
     ...

При этом следует иметь в виду, что смерть объекта ведет к смерти всех объектов, на которые данный объект хранит ссылки.

Тогда, например, если имеется дерево, построенное на основе ссылок на объекты, то потеря указателя на корень дерева ведет к смерти всех объектов, входящих в дерево.

Следует, однако, иметь в виду некоторые исключения из этого правила: Как показал опыт, если объект хранит указатель на окно (домен vpiDomains::windowHandle), то сборщик мусора будет рассматривать этот объект, как используемый.

То есть физически окно может быть закрыто, но не удаление указателя на это окно оставит объект в памяти. Аналогично часто указатели на COM-объекты могут быть причиной не освобождения памяти сборщиком мусора.

Существует один замечательный предопределённый интерфейс, который, как и все интерфейсы является и доменом. Это интерфейс - object.

Все экземпляры, принадлежащие доменам своих интерфейсов, в то же время являются дочерними доменами домена object. Это значит, что практически возможно вместо

facts
мойУказатель_V:мойИнтерфейс:=erroneous.

объявлять

facts
мойУказатель_V:object:=erroneous.

При этом будет компилироваться и работать

clauses
  мойПример():-
     мойУказатель_V:=мойОбъект::new(),
     ...

Но вот вызов

     ...
     мойУказатель_V:мойПредикатИзМойОбъект(...),
     ...

в этом случае не пропустит компилятор. Вместо этого необходимо производить конвертацию

     ...
     МойУказатель=convert(мойИнтерфейс,мойУказатель_V),
     МойУказатель:мойПредикатИзМойОбъект(...),
     ...

Примерно то же происходит, если интерфейс поддерживает другие интерфейсы. Сборщик мусора можно подтолкнуть предикатом

memory::garbageCollect()

но обычно в этом нет никакой необходимости.

Финализатор

Существует специальный предикат, который вызывается в момент гибели объекта, перед освобождением памяти объекта. Этот предикат - finalize. Его нельзя объявить, но для объектных классов можно написать реализацию (клауз). Следует иметь в виду, что момент, когда память будет фактически освобождена, и будет вызван предикат finalize (если он есть), не контролируется и определяется внутренней логикой сборщика мусора и его взаимодействием с операционной системой. Также нельзя делать никаких предположений о порядке освобождения объектов и, соответственно, о порядке вызова finalize для разных объектов. Рекомендуется предикат finalize использовать исключительно для освобождения ресурсов, при этом объектные факты освобождать нет никакой необходимости. Раз уж объект удаляется, то и все его факты постигнет аналогичная участь. Так же не рекомендуется закладывать в финализатор выполнение каких-нибудь логических действий. Например, если закрытие файла делать в финализаторе, то файл будет отрыт, а доступ других программ будет заблокировано, до неизвестного момента, пока сборщик мусора соизволит удалить данный объект. Следует учитывать и то, что финализатор может быть вызван на не полностью инициализированном объекте. Например, если произошёл сбой при выполнении конструктора, то не все факты будут правильно инициализированы. Поэтому всегда следует проверять факт, например на erroneous.

Ещё одно замечание, нельзя заранее сказать на каком потоке (thread) будет выполняться сборщик мусора (неявный вызов) и, соответственно, выполняться финализаторы. Распространение исключения из финализатора может повредить логику работы сборщика. В связи с эти все исключения из финализаторов перехватываются try/catch и игнорируется. Т.е. как будто вызов финализатора происходит так:

try
  Object:finalize()
catch _ do
  succeed()
end try