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.
精彩评论