Object Pascal: Must all objects (classes) be freed

2019-08-27 15:51发布

问题:

Can I throw around classes without freeing them, or will my software start spouting leaks?

For example can I do this

Engine := TEngine.Create(TV);

Then get rid of the reference without any problems, or must I call its Free method first?

Or have a function that returns a TSomething and not having to free its reference later on?

回答1:

The general rule is that if you create it, you should free it. The best way is in a try..finally if you're creating it in code:

var
  Engine: TEngine;
begin
  Engine := TEngine.Create(TV);
  try
    // Do stuff with Engine
  finally
    Engine.Free;
  end;
end;

The exception to this is if you have an object that accepts an owner as a parameter (such as a visual control like TEdit or a non-visual descendant of TComponent). If you assign the owner, it will free it when the owner is freed. (If you create it without an owner, you still have to free it yourself.)

procedure TForm1.FormCreate(Sender: TObject);
var
  EditA, EditB: TEdit;
begin
  EditA := TEdit.Create(Self);  // You're passing the form as owner; don't free
  EditB := TEdit.Create(nil);   // Creating without an owner; you free.
end;

If the class is a member (field) of another object, you create it in the containing objects constructor and free it in its destructor:

type
  TOuterClass = class(TObject)
  private
    FEngine: TEngine;
  public
    constructor Create;
    destructor Destroy; override;
  end;

implementation

constructor TOuterClass.Create;
begin
  inherited;
  FEngine := TEngine.Create(TV);
end;

destructor TOuterClass.Destroy;
begin
  FEngine.Free;
  inherited;
end;


回答2:

Technically yes, everything that you initialize with a constructor must be freed explicitly.

But there are some easy workarounds that when used properly, can save you most of the trouble:

1: Use TInterfacedObject:

  IMyStuff = interface(IUnknown)
    ['{9DF82155-2475-4403-8933-969DC4912AD7}']
    function Print:boolean;
    procedure DoStuff;
  end;

  TMyStuff = class(TInterfacedObject, IMyStuff)
    private
      function Print:boolean;
      procedure DoStuff;
   end;

Implement TMyStuff just like any other class. But when you USE that class in your code, use a variable of type IMyStuff, like this:

procedure MyIProcedure;
var myStuff: IMyStuff;
begin

   myStuff:=TMyStuff.create;
   myStuff.DoStuff;

end;

No need to cast in the 'TMyStuff.create' call (in this case - sometimes it is...) - since the variable is type IMystuff this is implicit. No need to free IMyStuff ( In fact you can't, although you can call IMyStuff:=nil.) Since it is declared as an interface type, automated garbage collection is implemented using the the COM reference counting model - Delphi handles this for you when you inherit from TInterfacedObject.

BUT do not mix class type variables: i.e. TMyStuff with IMyStuff variable types. This can lead to some nasty confusion and errors that will leave you scratching your head. That is the reason I generally declare members of TInterfacedObject as PRIVATE, as I did here, making them in inaccessible via references to TMyStuff. However they are accesible via a reference to IMyStuff: all interface members are public, by definition.

2: Besides Ken's excellent answer, the VCL's TObjectList and TObjectDictionary provide automated garbage collection for all the object references they contain (see Delphi docs for details) - but they themselves must be freed, and then the rest gets freed with them.

Use TObjectList or TObjectDictionary in a TInterfacedObject and never worry about garbage collection again, as long as you free the ObjectList or ObjectDictionary in the destructor of your TInterfacedObject. That destructor will be called automatically by Delph's implementation when referenceCount=0.