Как БЕСПЛАТНО вложенные (тип объекта) поля классов? - PullRequest
2 голосов
/ 02 июля 2011
TBaseClass = class
public
  destructor Destroy; override;
end;

TFirstClass = class(TBaseClass)
  FMyProp: string;
end;

TSecondClass = class(TBaseClass)
  FMyFirstClass: TFirstClass;
end;

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

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

Я пытаюсь использовать новый RTTI, чтобы получить все поля на основе TBaseClass, но я не могу получить экземпляр поля объекта, и у меня нет идей.

Собираюсь ли я на правильный путь?Что вы предлагаете делать?

Ответы [ 5 ]

8 голосов
/ 02 июля 2011

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

3 голосов
/ 02 июля 2011

Объекты Delphi всегда должны «принадлежать» другому объекту, а объект-владелец отвечает за освобождение принадлежащего объекта при вызове его Destructor. Так как это делается для каждого класса, не должно быть никаких проблем. Вы могли бы использовать RTTI для этого, но это было бы тратой времени и ресурсов, поскольку писать деструкторы очень просто.

Например, базовый класс вводит два поля типа TObject:

TBaseClass = class
private
public
  OwnedObject: TObject;
  NotOwnedObject: TObject;

  destructor Destroy;override;
end;

destructor TBaseClass.Destroy;override;
begin
  OwnedObject.Free; // TBaseClass owns this object, it should be destroyed when
                   // TBaseClass is destroyed.
  // Do NOT call NotOwnedObject.Free: that object is not owned by TBaseClass,
  // a different object is responsible for calling Free on it.

  // ALWAYS call "inherited" from your destructor, it allows daisy-chaining destructors.
  // more on this in the next example.
  inherited;
end;

Классы-потомки, которые вводят новые поля, должны переопределить деструктор и освободить эти объекты, затем вызвать inherited, чтобы дать родителю шанс освободить введенные им объекты. Пример:

TFirstClass = class(TBaseClass)
public
  AnOtherOwnedObject: TObject;
  AnOtherNotOwnedObject: TObject;

  destructor Destroy;override;
end;

destructor TFirstClass.Destroy;override;
begin
  // Free the stuff we owned and *we* introduced:
  AnOtherOwnedObject.Free;

  // Call the inherited destructor so the base class can free fields they introduced:
  inherited;
end;

В своем вопросе вы говорите:

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

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

  • Как говорит Барри Келли, вам не нужно проверять nil перед вызовом .Free, потому что Free делает это сам.
  • Как говорит Дэвид Хеффернан, все, что вам нужно сделать, это следовать этой схеме везде, и у вас все будет хорошо, никаких утечек памяти. Так VCL всегда работал!
  • Как говорит mjn, обратите внимание, чтобы не переопределять принадлежащие объекты другими объектами, не освобождая их: когда вы делаете это, вы теряете последнюю ссылку на старый объект и больше не можете его освободить, это гарантированная утечка памяти.
1 голос
/ 02 июля 2011

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

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

1 голос
/ 02 июля 2011

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

0 голосов
/ 03 июля 2011

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

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

program BaseFree;

{$APPTYPE CONSOLE}

uses
  Classes, SysUtils;

type
  TBase = class(TObject)
  strict private
    class var Instances: TList;
    class var Destroying: boolean;
    class procedure Clear(Instance: TBase);
  private
    class procedure Debug;
  protected
    class constructor Create;
    class destructor Destroy;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TItem = class(TBase)

  end;

{ TBase }

class procedure TBase.Clear(Instance: TBase);
var
  I: Integer;
  Item: TBase;
begin
  for I := 0 to TBase.Instances.Count -1 do
  begin
    Item:= TBase.Instances[I];
    if Item <> Instance then
      Item.Destroy;
  end;
  TBase.Instances.Clear;
end;

class constructor TBase.Create;
begin
  TBase.Instances:= TList.Create;
end;

constructor TBase.Create;
begin
  if TBase.Destroying then
    // instead of raise Exception, you can "wait" and start a new cycle.
    raise Exception.Create('Cannot Create new instances while Destrying.');

  inherited Create;
  TBase.Instances.Add(Self);
end;

class procedure TBase.Debug;
begin
  Writeln('TBase.Instances.Count: ', TBase.Instances.Count);
end;

destructor TBase.Destroy;
begin
  if not TBase.Destroying then
  begin
    TBase.Destroying:= True;
    try
      TBase.Clear(Self);
    finally
      TBase.Destroying:= False;
    end;
  end;

  inherited;
end;

class destructor TBase.Destroy;
begin
  TBase.Clear(nil);
  TBase.Instances.Free;
end;

procedure CreateItems(Count: Integer);
var
  I: Integer;
  Item: TItem;
begin
  TBase.Debug;
  for I := 0 to Count -1 do
    TItem.Create;
  TBase.Debug;
  Item:= TItem.Create;
  TBase.Debug;
  Item.Free;
  TBase.Debug;
end;

begin
  ReportMemoryLeaksOnShutdown:= True;
  try
    CreateItems(100);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Writeln('Press <ENTER> to finish.');
  ReadLn;
end.
...