Подсчет ссылок для объектов - PullRequest
7 голосов
/ 23 апреля 2009

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

type TFileInfo = class (TInterfacedObject, IInterface)

и удалил все мои ручные вызовы TFileInfo.Free. К сожалению, Delphi сообщила о многих утечках памяти. Ища SO, я нашел следующий вопрос, объясняющий, почему это не работает:

Почему не собираются потомки TInterfacedObject?

Там представлен обходной путь, но он требует от меня (по крайней мере, если я правильно понял) написания пользовательского интерфейса IFileInfo и предоставления ему большого количества методов получения и установки, которых я хочу избежать.

РЕДАКТИРОВАТЬ Я должен добавить, что я вставляю объекты создания FileInfo в два вида хеш-таблиц: одна происходит от TBucketList, а другая - реализация хэш-карты с форума Codegear. Внутренне они оба являются указателями пользователей, поэтому ситуация такая же, как и в другом вопросе.

Есть ли какая-либо другая возможность заставить объекты в Delphi использовать подсчет ссылок?

Ответы [ 7 ]

8 голосов
/ 23 апреля 2009

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

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

  1. Барри Келли написал пост о Smart Pointers . Он использует Generics в Delphi 2009, но я уверен, что вы можете жестко закодировать его для конкретных версий типа, который вы используете, если вы еще не используете 2009 (это отличный выпуск BTW).

  2. Другим способом, который работает с большим количеством версий Delphi и меньшим количеством модификаций, является оболочка типа значения от Janez Atmapuri Makovsek. Это пример, реализованный для TStringList, но вы можете адаптировать его для любого типа.

  3. Третий способ - создать интерфейсный указатель (похож на Smart Pointer Барри, но не такой умный). Я считаю, что в JCL есть такой, но я точно не помню деталей. По сути, это интерфейс, который принимает ссылку TObject при создании. Затем, когда счетчик ссылок достигает нуля, он вызывает free для объекта, который вы ему передали. Этот метод действительно работает только для кратковременных экземпляров, которые вы не передаете в качестве параметров, потому что вы отделяете ссылочную ссылочную ссылку от фактически используемой ссылки. Вместо этого я бы порекомендовал один из двух других методов, но если вы предпочитаете этот метод и хотите больше информации, просто дайте мне знать.

Это то, что в Delphi, есть свободные способы выполнения вещей. Вариант № 1, на мой взгляд, лучший - приобретите Delphi 2009 и используйте этот метод, если можете.

Удачи!

5 голосов
/ 23 апреля 2009

К сожалению, компилятор Delphi генерирует необходимый код для подсчета ссылок inc / dec только при использовании интерфейсов (в вашем случае пользовательский интерфейс IFileInfo). Более того, если интерфейсы приводятся к указателю (или TObject в этом отношении), опять же, подсчет ссылок невозможен. Например, предполагая список глобальных переменных: TList:

var ifi : IFileInfo;
begin
  ifi := TFileInfo.Create;
  list.Add(TFileInfo(ifi));
end;

после того, как метод вернет список [list.Count - 1] будет содержать висячий указатель.

Так что интерфейсы нельзя использовать в хэш-карте, которая приводит их к указателям, реализация хеш-карты должна сохранять их как IInterface.

3 голосов
/ 23 апреля 2009

Если вы хотите исключить вызовы free в экземплярах TObject, вам может понадобиться поискать сборщик мусора для нативного Delphi. Мне известны 2 разных сборщика мусора и техника сбора мусора, у каждого из которых есть свои плюсы и минусы.

Один из них, вероятно, будет работать для вас.

3 голосов
/ 23 апреля 2009

Не смешивайте ссылки на объекты и ссылки на интерфейсы.

var
  Intf: IInterface;
  Obj: TFileInfo;

begin
  // Interface Reference
  Intf := TFileInfo.Create; // Intf is freed by reference counting, 
                            // because it's an interface reference
  // Object Reference
  Obj := TFileInfo.Create;
  Obj.Free; // Free is necessary

  // Dangerous: Mixing
  Obj := TFileInfo.Create;
  Intf := Obj; // Intf takes over ownership and destroys Obj when nil!
  Intf := nil; // reference is destroyed here, and Obj now points to garbage
  Obj.Free; // this will crash (AV) as Obj is not nil, but the underlying object
            // is already destroyed
end;
3 голосов
/ 23 апреля 2009

Эта функциональность предоставляется для интерфейсов, но не для объектов.

Вы можете создать что-то подобное, но вам нужно переопределить некоторые структуры TObject:

TRefCountObject = class (TObject)
private
  FRefCount : Integer;
public
  constructor Create;

  procedure Free; reintroduce;

  function RefCountedCopy: TRefCountObject;
end;


constructor TRefCountObject.Create;
begin
  inherited;
  FRefCount := 1;
end;

procedure TRefCountObject.Free;
begin
  if self=nil then Exit;
  Dec(FRefCount);
  if FRefCount<=0 then
    Destroy;
end;

function TRefCountObject.RefCountedCopy: TRefCountObject;
begin
  Inc(FRefCount);
  Result := self;
end;

Вам необходим RefCountingCopy, чтобы назначить объект другой переменной. Но тогда у вас есть пересчитанный объект.

Как это использовать:

var1 := TRefCountObject.Create;   // rc = 1
var2 := var1.RefCountedCopy;      // rc = 2
var3 := var1.RefCountedCopy;      // rc = 3
var2.Free;                        // rc = 2
var1.Free;                        // rc = 1
var4 := var3.RefCountedCopy;      // rc = 2
var3.Free;                        // rc = 1
var4.Free;                        // rc = 0
1 голос
/ 23 апреля 2009

Чтобы добавить к тому, что уже было сказано, если вы хотите хранить ссылки на интерфейсы, вместо использования TList используйте TInterfaceList . Счетчик ссылок будет работать последовательно.

0 голосов
/ 23 апреля 2009

Это длинное объяснение, но вкратце: наследования от TInterfacedObject (и без вызова Free самостоятельно) недостаточно, вам нужно использовать динамическую объектную фабрику для создания объектов и использовать интерфейс указатели на объект везде, а не только на объект-ссылку-переменные. (Да, это означает, что вы не можете просто переключить «старый код», не просматривая его)

...