Ошибка времени жизни, которую я чаще всего встречаю в старом коде на Delphi, возникает, когда на одном экземпляре смешивают два правила владения. Класс, унаследованный от TInterfacedObject, освобождается, когда счётчик ссылок на интерфейс доходит до нуля. Переменная классового типа не считается вовсе. Если держать один экземпляр через оба вида ссылок, правила расходятся в том, когда он умирает.
program Greeter;
{$APPTYPE CONSOLE}
type
IGreeter = interface
procedure Hello;
end;
TGreeter = class(TInterfacedObject, IGreeter)
procedure Hello;
end;
procedure TGreeter.Hello;
begin
WriteLn('hello');
end;
procedure Use(G: IGreeter);
begin
G.Hello;
end;
var
Obj: TGreeter;
begin
Obj := TGreeter.Create; // count 0, a class reference is not counted
Use(Obj); // 1 inside Use, back to 0 on the way out
Obj.Free; // the object is already gone
end.
Экземпляр начинает со счётчиком ноль, потому что ссылка на класс его не трогала. При передаче в Use он преобразуется в IGreeter, на время вызова счётчик становится единицей, на выходе возвращается к нулю, и на нуле объект уничтожает себя сам. Следующий за этим Obj.Free - повторное уничтожение уже освобождённой памяти. Компилятор всё это принимает.
Моё правило таково. Если класс реализует интерфейсы через TInterfacedObject, то каждая долгоживущая ссылка на экземпляр - интерфейсная, а классовый тип появляется только в строке с вызовом конструктора. Любопытно, проводят ли другие черту там же.
