📜 ⬆️ ⬇️

Delphi Automatic destruction of objects at the exit of the method

Good afternoon.

I think all Delphi have written similar code thousands of times:

var
MyObj : TMyObj ;
begin
MyObj := TMyObj . Create ;
try
MyObj . DoWork ; // MyObj
finally
MyObj . Free ;
end ;
end ;

')
Let's just say: such writings annoying. It would be desirable, that objects were destroyed at an exit from function / procedure.
Well, it's easy enough to implement. As a result, we get something like this:
var
MyObj : TMyObj ;
begin
MyOb := CreateObjectDestroyer(TMyOb . Create) . ObjectAsPtr ;
MyObj . DoWork ; // MyObj
end ; // MyObj


Below I will describe how to implement this behavior.


Immediately I will point out that all the code below will work in all versions of the Delphi that have interfaces (in my opinion, starting from the third).

As is well known for interfaces, Delphi maintains automatic reference counting:

var
Intf : IUnknown ;
begin
Intf := TInterfacedObject . Create ; // IUnknown.AddRef
//
end ; // IUnknown.Release


Now we write a descendant of TInterfacedObject, which will destroy the object passed to it in its destructor. Well, plus our object will implement a small additional interface.

type
IObjectDestroyer = interface (IInterface)
[ '{4DE81104-08B2-4821-960E-8935AC9B8F5E}' ]
function GetObjectAsPtr : Pointer ;
property ObjectAsPtr : Pointer read GetObjectAsPtr ; // ,
end ;

type
TObjectDestroyer = class (TInterfacedObject , IObjectDestroyer)
strict private
FObject : TObject ;
protected
function GetObjectAsPtr : Pointer ;
public
constructor Create (AObject : TObject ) ;
destructor Destroy ; override ;
end ;

constructor TObjectDestroyer . Create (AObject : TObject ) ;
begin
inherited Create() ;
FObject := AObject ;
end ;

destructor TObjectDestroyer . Destroy ;
begin
FObject . Free ;
inherited Destroy ;
end ;

function TObjectDestroyer . GetObjectAsPtr : Pointer ;
begin
Result := FObject ;
end ;


Now you can work with the object like this:

var
Destroyer : IObjectDestroyer ;
begin
Destroyer := TObjectDestroyer . Create(TMyObj . Create) ;
TMyObj(Destroyer . ObjectAsPtr) . DoWork() ;
end ;


We got rid of the need to explicitly destroy MyObj, but the need to write TMyObj (Destroyer.ObjectAsPtr) to access the object is mildly depressing. Well, let's describe the additional function:

function CreateObjectDestroyer (AObject : TObject ) : IObjectDestroyer ;
begin
Result := TObjectDestroyer . Create(AObject) ;
end ;


and remove the Destroyer variable that we don't need from our code in general:

var
MyObj : TMyObj ;
begin
MyOb := CreateObjectDestroyer(TMyOb . Create) . ObjectAsPtr ;
MyObj . DoWork ;
end ;


The call to CreateObjectDestroyer implicitly create a variable (what we described as the Destroyer in the previous version).

Well, the problem itself has been solved, but I would like to mention some unobvious points and overt shortcomings of this approach:

So the first is typing. More precisely, its absence. The ObjectAsPtr property is specifically declared as a Pointer so that it can be assigned to a variable of any class:

var
Obj1 : TMyObj1 ;
Obj2 : TMyObj2 ;
begin
Obj1 := CreateObjectDestroyer(TMyObj1 . Create) . ObjectAsPtr ;
Obj2 := CreateObjectDestroyer(TMyObj2 . Create) . ObjectAsPtr ;
end ;


but in this case, the compiler does not keep track of the types and we can nakosyachit:
Obj1 := CreateObjectDestroyer(TMyObj2 . Create) . ObjectAsPtr ;

most likely in this case, waiting for Access violation

You can add the AObject: TObject property to the IObjectDestroyer interface and explicitly type
Obj1 := CreateObjectDestroyer(TMyObj2 . Create) . AObject as TMyObj1 ;


here the error will be more obvious, but still it is a run-time check. In the compile time for the Delphi younger than 2009 version, this problem is not solved, in the older versions there are generics, there you can don’t donate typing.

The second problem: the implicit moment of the destruction of the object.

In theory, the compiler can call Release for immediately after calling CreateObjectDestroyer
Obj: = CreateObjectDestroyer (TMyObj.Create) .ObjectAsPtr;
// The Obj link is invalid since the interface is released and the MyObj object is destroyed.

However, Barry Kelly says that such behavior will not change in the future, you can read the discussion here.

Again, as with the issue of typing in 2009/2010, the delph can partially solve this drawback.

Well, in general, that's all, if you like this post, I will describe how to more transparently implement a similar mechanism on older versions of the Delphi.

PS Advise analogue source code highlighter, which would support the Delphic syntax?

Source: https://habr.com/ru/post/87692/


All Articles