开发者

How to FREE nested (object type) field classes?

TBaseClass = class
public
  destructor Destroy; override;
end;

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

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

I need to implement a DESTRUCTOR that can be able to find all (object type) fields from the same base class and give a Free on it to avoid all those memory leaks.

Why? Because FMyFirstClass be can created or not, that depends on the flow of my app and开发者_如何学JAVA I can't garantee when it will be created to Free it, neither do want to fill all destructors with a NIL check kind of code, because i have a lots of fields like that.

I'm trying to use the new RTTI to get all fields based on TBaseClass, but I can't get the instance of the object-field and I'm out of ideas.

Am I going to the right way? What you suggest to do?


Calling Free has no effect on a nil instance. It's deliberately designed that way. Your destructors should call Free on any fields of object types which it logically owns, irrelevant of whether the object has been constructed or not.


Delphi objects should always be "Owned" by an other object, and the owner object is responsible for freeing the owned object when it's Destructor is called. Since this is done for each and every class, there shouldn't be any problems. You could use RTTI for this, but it'd be a waist of time and resource, since writing Destructors is so easy.

Example, base class introduces two TObject-type fields:

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;

Descendant classes that introduce new fields should override the destructor and free those objects, then call inherited to give the parent a chance to free the objects it introduced. Example:

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;

In your question you say:

I need to implement a DESTRUCTOR that can be able to find all (object type) fields from the same base class and give a Free on it to avoid all those memory leaks.

As you can see, that's not the way to do it: The derived class's destructor calls inherited so the base class gets it's chance to free the fields it introduced.

  • As Barry Kelly says, you don't need to check for nil before calling .Free, because Free does that itself.
  • As David Heffernan says, all you need to do is follow this pattern everywhere and you'll be fine, no memory leaks. This is how the VCL always worked!
  • As mjn says, pay attention not to override owned objects with other objects without freeing them: when you do that you lose the last reference to the old object and you can't free it any more, it's a guaranteed memory leak.


I don't think you are going the right way. Just free the objects in the class you declare/create them. And as Barry said, you don't need to have nil checks.


The advice you have received in the other answers is correct. Make sure that you pair up each object instantiation with a corresponding Free in the destructor.

I'd like to point out why you can't use RTTI to look up all the object instances and free them. Some of the object instances may not be owned by the object that is being destroyed. If you have a field containing a reference to an object that is owned by some other entity in your system, then you are not at liberty to destroy it. RTTI cannot tell you who owns the object, only you can know that.


You are doing it in the wrong way. You have to save the reference to know what you have to free, if you don't know the flow, then your design is fault.

If your needs is something unusual, you can refactory the base class to hold all references, check if this code can help you:

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.
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜