问题
If you will look at the code of FreeAndNil procedure you will see:
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
What is the reason they assigning Nil
to an object reference and only after this destroying it? Why not vice-versa?
回答1:
I can think of two reasons for doing it this way round, neither of which seems at all compelling.
Reason 1: to guarantee that the reference is set to nil in case an exception is raised
The implementation achieves this. If the destructor raises, then the reference is still set to nil. Another way to do so would be with a finally
block:
try
TObject(Obj).Free;
finally
TObject(Obj) := nil;
end;
The downside of this is performance. Particularly on x86 a try/finally
is a little expensive. In such a fundamental routine it is prudent to avoid this expense.
Why do I find the desire to nil at all costs not to be compelling? Well, as soon as destructor start failing you may as well give up. You can no longer reason clearly about your program's state. You cannot tell what failed and what state your program is in. It is my view that the correct course of action in the face of a destructor that raises is to terminate the process.
Reason 2: to ensure that other threads can detect that the object is being destroyed
Again this is achieved but it is of no practical use. Yes you can test whether the reference is assigned or not. But then what? The other thread cannot call methods on the object without synchronization. All you could do is learn whether or not the object is alive. And if that is so, why would it matter if this status changed before or after the destructor runs?
So whilst I present this as a possible reason I cannot believe that anyone in Embarcadero was really swayed by this argument.
回答2:
There's a variation on David's second reason that is a little more compelling. Although one might argue that if it applies there are other problems that should be fixed.
Reason 3: to ensure event handlers on the same thread can detect that the object is being destroyed
Here's a concocted hypothetical example:
TOwner.HandleCallback;
begin
if Assigned(FChild) then
FChild.DoSomething;
end;
TChildClass.Destroy;
begin
if Assigned(FOnCallback) then FOnCallback;
inherited Destroy;
end;
Now if TOwner calls:
FChild.Free;
FChild := nil;
FChild
will be asked to DoSomething
in the middle of its destruction cycle. A certain recipe for disaster. The implementation of FreeAndNil
neatly avoids this.
Yes you may argue that firing callback events during destruction is dangerous, but it does have its benefits. There are quite few examples in Delphi/VCL code. Especially if you expand the scope of concern to include calling polymorphic methods - which also put aspects of the destruction sequence outside of your control.
Now I must point out that I'm not aware of any specifc cases in Delphi library code where this problem could manifest. But there are some complex circular dependencies in parts of the VCL. So I wouldn't be surprised if changing the implementation to the more obvious choice in SysUtils leads to a few unpleasant surprises.
回答3:
The only thing that really bothered me for years is that FreeAndNil(var obj)
lacks type safety. I know that due to the lack of adequate language support there was simply no way to do it right, but since we have generics for a while, here's a quick and simple tool that can make your life easier.
type
TypeSafe = class sealed
public
class procedure FreeAndNil<T : class>( var obj : T); static; inline;
end;
class procedure TypeSafe.FreeAndNil<T>( var obj : T);
begin
SysUtils.FreeAndNil( obj);
end;
If you add an overload for the normal FreeAndNil(var Obj)
and mark it as deprecated
you got a fair chance to find all the places where someone hands over an interface pointer to it -- and if the code base is only large enough you will find interesting things, believe me.
procedure FreeAndNil(var Obj); inline; deprecated 'use TypeSafe.FreeAndNil<T>() instead';
Note that you don't even have to specify the <T>
since the compiler is smart enough to find out the right type for you. Just add the unit on top and change all
FreeAndNil(foobar);
in your source code into
TypeSafe.FreeAndNil(foobar);
and you're done.
来源:https://stackoverflow.com/questions/26623852/why-freeandnil-implementation-doing-nil-before-free