Механизм подсчета ссылок на объект предназначен для автоматического уничтожения неиспользуемых объектов. Неиспользуемым считается объект, на который не ссылается ни одна интерфейсная переменная.
Подсчет ссылок на объект обеспечивают методы _AddRef и _Release интерфейса IInterface. При копировании значения интерфейсной переменной вызывается метод _AddRef, а при уничтожении интерфейсной переменной — метод _Release. Вызовы этих методов генерируются компилятором автоматически:
var Intf, Copy: IInterface;begin ... Copy := Intf; // Copy._Release; Intf._AddRef; Intf := nil; // Intf._Release; end; // Copy._Release |
Стандартная реализация методов _AddRef и _Release находится в классе TInterfacedObject. Она достаточно проста и вы легко разберетесь с ней, читая комментарии в исходном тексте.
type TInterfacedObject = class(TObject, IInterface) ... FRefCount: Integer; // Счетчик ссылок function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; ... end; function TInterfacedObject._AddRef: Integer;begin Result := InterlockedIncrement(FRefCount); // Увеличение счетчика ссылокend; function TInterfacedObject._Release: Integer;begin Result := InterlockedDecrement(FRefCount); // Уменьшение счетчика ссылок if Result = 0 then // Если ссылок больше нет, то Destroy; // уничтожение объектаend; |
Заметим, что функции InterlockedIncrement и InterlockedDecrement просто увеличивают значение целочисленной переменной на единицу. В отличие от обычного оператора сложения, они обеспечивают атомарное изменение значения переменной, что очень важно для правильной работы распараллеленных (многопоточных) программ.
Приведенную выше реализацию методов _AddRef и _Release автоматически получают все наследники класса TInterfacedObject, в том числе и классы TTextReader, TDelimitedReader и TFixedReader. Поэтому неиспользуемые объекты классов TDelimitedReader и TFixedReader тоже автоматически уничтожаются при работе с ними через интерфейсные переменные:
var Obj: TDelimitedReader; Intf, Copy: ITextReader;begin Obj := TDelimitedReader.Create('MyData.del', ';'); Intf := Obj; // Obj._AddRef -> Obj.FRefCount = 1 Copy := Intf; // Obj._AddRef -> Obj.FRefCount = 2 ... Intf := nil; // Obj._Release -> Obj.FRefCount = 1 Copy := nil; // Obj._Release -> Obj.FRefCount = 0 -> Obj.Destroy Obj.Free; // Ошибка! Объект уже уничтожен и переменная Obj указывает в никудаend; |
Обратите внимание, что объектные переменные не учитываются при подсчете ссылок. Поэтому мы настоятельно рекомендуем избегать смешивания интерфейсных и объектных переменных. Если вы планируете использовать объект через интерфейс, то лучше всего результат работы конструктора сразу присвоить интерфейсной переменной:
var Intf: ITextReader;begin Intf := TDelimitedReader.Create('MyData.del', ';'); // FRefCount = 1 ... Intf := nil; // FRefCount = 0 -> Destroyend; |
Если интерфейс является входным параметром подпрограммы, то при вызове подпрограммы создается копия интерфейсной переменной с вызовом метода _AddRef:
procedure LoadItems(R: ITextReader);begin...end; var Reader: ITextReader;begin ... LoadItems(Reader); // Создается копия переменной Reader и вызывается Reader._AddRefend; |
Копия не создается, если входной параметр описан с ключевым словом const:
procedure LoadItems(const R: ITextReader);begin...end; var Reader: ITextRedaer;begin ... LoadItems(Reader); // Копия не создается, метод _AddRef не вызывается end; |
Интерфейсная переменная уничтожается при выходе из области действия переменной, а это значит, что у нее автоматически вызывается метод _Release:
var Intf: ITextRedaer;begin Intf := TDelimitedReader.Create('MyData.del', ';'); ...end; // Intf._Release |