Back to Home

Delphi. Automatic destruction of objects upon exiting the method

delphi

Delphi. Automatic destruction of objects upon exiting the method

    Good afternoon.

    I think all Delphians wrote a 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 a scribble bothers. I want the objects themselves to be destroyed when they exit the function / procedure.
    Well, this is easy enough to implement. As a result, we get something like this code:
    var
      MyObj: TMyObj;
    begin
      MyOb := CreateObjectDestroyer(TMyOb.Create).ObjectAsPtr;
      MyObj.DoWork;   // работаем с MyObj
    end// тут объект MyObj уничтожится


    Below I will describe how to implement this behavior.


    I’ll point out right away that all the code below will work in all versions of the dolphin that have interfaces (in my opinion, starting from the third).

    As is known for interfaces, Delphi maintains an automatic reference count:

    var
      Intf: IUnknown;
    begin
      Intf := TInterfacedObject.Create; // тут неявно вызывается IUnknown.AddRef
      // что то делаем
    end// тут неявно вызывается IUnknown.Release и объект разрушается 


    Now let's write the TInterfacedObject descendant, 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) is depressing to say the least. Well, let's describe an additional function:

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


    and remove from our code, in general, the Destroyer variable we do not need:

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


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

    Well, actually the problem is solved, but I would like to mention some unobvious moments and frank shortcomings of this approach:

    So, the first is typing. More precisely, its absence. the ObjectAsPtr property is specifically declared as 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 monitor the types and we can mess up:
    Obj1 :=  CreateObjectDestroyer(TMyObj2.Create).ObjectAsPtr;

    most likely in this case we are waiting for Access violation.

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


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

    The second problem: the implicit moment of 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 because the zarelizin interface and the MyObj object are destroyed.

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

    Again, like the issue of typing in the 2009/2010 dolph, this drawback can be partially solved.

    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 dolphin.

    PS Advise an analog of source code highlighter, which would support the Delphic syntax?

    Read Next