Reputation: 402
I have an object which is assigned to multiple TStringList
objects.
Releasing memory for first TStringList
works fine.
But when i call Object.Free for the second TStringList
, it results in Access Violation.
This is how the object is assigned to the 2 TStringList
SL1.AddObject(S, Person);
if (Sl2<> nil) and (Var1 <> '')
then Sl2.AddObject(Var1,TObject(Person));
This is how memory is released.
SL1
Method:ReleaseSL1
For J := SL1.Count - 1 downto 0 do
begin
if (SL1.Objects[J]) <> nil then
begin
PersonObj:= TPerson(SL1.Objects[J]);
PersonObj.Free;
SL1.Objects[J] := nil;
end;
end;
SL2
Method:ReleaseSL2
for I := 0 to SL2.Count-1 do
if SL2.Objects[I] <> nil then
begin
PersonObj := TPerson(SL2.Objects[I]);
PersonObj .Free;
SL2.Objects[I] := nil;
end;
SL2.Clear;
Here, when PersonObj.Free is executed in method ReleaseSL2, it results in Access Violation
Is there any way, apart from restructuring of code, that I can avoid this Access Violation?
Note: There are many more objects assigned to both SL1 and SL2 and the 2 TStringList
objects are not identical
Upvotes: 1
Views: 885
Reputation: 597111
Is there any way, apart from restructuring of code, that I can avoid this Access Violation?
No. A restructure is required. You need to clearly define who owns the TPerson
objects, and who merely refers to them. In your example, SL1
can own the objects and SL2 should refer to them. Free the objects only from the list that owns them, and remove any references from the list that refers to them without freeing them again.
SL1.AddObject(S, Person); // <--assumed ownership
if (Sl2 <> nil) and (Var1 <> '') then
Sl2.AddObject(Var1, Person); // <-- reference only
For J := SL1.Count - 1 downto 0 do
begin
if (SL1.Objects[J]) <> nil then
begin
TPerson(SL1.Objects[J]).Free;
SL1.Objects[J] := nil;
end;
end;
//...
SL2.Clear;
Otherwise, you need to use a third list that owns the objects, and then have both SL1 and SL2 merely refer to them:
Objects.Add(Person); // <-- asssumed ownership
SL1.AddObject(S, Person); // <-- reference only
if (Sl2 <> nil) and (Var1 <> '') then
Sl2.AddObject(Var1, Person); // <-- reference only
For J := SL1.Count - 1 downto 0 do
SL1.Objects[J] := nil;
//...
SL2.Clear;
//...
For J := 0 to Objects.Count - 1 do
TPerson(Objects[J]).Free;
Objects.Clear;
If you use a TObjectList
or TObjectList<TPerson>
for the third list, you can set their OwnsObjects
property to True to auto-manage the lifetime of the TPerson
objects for you.
FYI, on modern Delphi versions, TStringList
also has an OwnsObjects
property, but I would not suggest using it in this situation.
Upvotes: 1
Reputation: 125728
You're freeing the object and only nilling one of the two references to that object (the one in SL1).
Consider the following test application:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TTest = class(TObject);
var
A, B: TTest;
begin
A := TTest.Create;
B := A;
WriteLn(Format('Assigned A: %s B: %s',
[BoolToStr(Assigned(A), True), BoolToStr(Assigned(B), True)]));
FreeAndNil(A);
WriteLn(Format('Assigned A: %s B: %s',
[BoolToStr(Assigned(A), True), BoolToStr(Assigned(B), True)]));
ReadLn;
end.
The output it produces:
Assigned A: True B: True
Assigned A: False B: True
Note that, even though you've called FreeAndNil
on A
, it doesn't automagically set the value of B
to nil as well. The compiler has no way of knowing that you're finished with the other reference.
The solution to your particular issue is simple: when freeing the object in SL1
, see if it exists in SL2
and nil it at the same time. (You also don't need the local variable PersonObj
. If you've properly overridden the inherited methods, the proper Free
will be called for you.)
var
RefIdx: Integer;
For J := SL1.Count - 1 downto 0 do
begin
if SL1.Objects[J] <> nil then
begin
RefIdx := SL2.IndexOfObject(SL1.Objects[J]);
if RefIdx > -1 then
SL2.Objects[RefIdx] := nil;
SL1.Objects[J].Free;
SL1.Objects[J] := nil;
end;
end;
Upvotes: 1
Reputation: 12610
To make sure you don't try to free
an object tiwce, you can use somehing like this:
For J := SL1.Count - 1 downto 0 do
begin
if (SL1.Objects[J]) <> nil then
begin
PersonObj:= TPerson(SL1.Objects[J]);
// Make sure this object is not kept in SL2 any more:
SL2.remove(PersonObj);
PersonObj.Free;
SL1.Objects[J] := nil;
end;
end;
for I := 0 to SL2.Count-1 do
if SL2.Objects[I] <> nil then
begin
PersonObj := TPerson(SL2.Objects[I]);
PersonObj .Free;
// Make sure this object is not kept in SL1 any more:
SL1.remove(PersonObj);
SL2.Objects[I] := nil;
end;
SL2.Clear;
One note: You only need to remove the object from the other list if that list is not already cleared.
Upvotes: 1