Search code examples
delphigenericsclonetpersistent

Best way to implement Clone() in my specific classes


In my specific TPersistent classes I'd like to provide a Clone function, which returns an independent copy of the object.

Is it possible to make this work correctly with descendants, without implementing the Clone function in each and every descendant?

This is not about cloning any unknown fields or deep cloning (which could be done using RTTI). In my minimal example below, you can see where I would want to put the Clone function.

Since it uses Assign() to copy the data, it would work with any descendant. The problem is the constructor, see comments. How do I call the correct constructor of that descendant? If that's very hard to do, it's okay to assume that none of the descendants override the constructor without overriding Clone, too.

program Test;

uses System.SysUtils, System.Classes;

type
  TMyClassBase = class abstract(TPersistent)
  public
    constructor Create; virtual; abstract;
    function Clone: TMyClassBase; virtual; abstract;
  end;

  TMyClassBase<T> = class abstract(TMyClassBase)
  private
    FValue: T;
  public
    constructor Create; overload; override;
    function Clone: TMyClassBase; override;
    procedure Assign(Source: TPersistent); override;
    property Value: T read FValue write FValue;
  end;

  TMyClassInt = class(TMyClassBase<Integer>)
  public
    function ToString: string; override;
  end;

  TMyClassStr = class(TMyClassBase<string>)
  public
    function ToString: string; override;
  end;

constructor TMyClassBase<T>.Create;
begin
  Writeln('some necessary initialization');
end;

procedure TMyClassBase<T>.Assign(Source: TPersistent);
begin
  if Source is TMyClassBase<T> then FValue:= (Source as TMyClassBase<T>).FValue
  else inherited;
end;

function TMyClassBase<T>.Clone: TMyClassBase;
begin
  {the following works, but it calls TObject.Create!}
  Result:= ClassType.Create as TMyClassBase<T>;
  Result.Assign(Self);
end;

function TMyClassInt.ToString: string;
begin
  Result:= FValue.ToString;
end;

function TMyClassStr.ToString: string;
begin
  Result:= FValue;
end;

var
  ObjInt: TMyClassInt;
  ObjBase: TMyClassBase;
begin
  ObjInt:= TMyClassInt.Create;
  ObjInt.Value:= 42;
  ObjBase:= ObjInt.Clone;
  Writeln(ObjBase.ToString);
  Readln;
  ObjInt.Free;
  ObjBase.Free;
end.

The output is

some necessary initialization
42

So, the correct class came out, it works correctly in this minimal example, but unfortunately, my necessary initialization wasn't done (should appear twice).

I hope I could make it clear and you like my example code :) - I'd also appreciate any other comments or improvements. Is my Assign() implementation ok?


Solution

  • You don't need to make the non-generic base class constructor abstract. You can implement the clone there, because you have a virtual constructor.

    Furthermore you don't need to make the Clone method virtual.

    type
      TMyClassBase = class abstract(TPersistent)
      public
        constructor Create; virtual; abstract;
        function Clone: TMyClassBase; 
      end;
    
    ... 
    
    type
      TMyClassBaseClass = class of TMyClassBase;
    
    function TMyClassBase.Clone: TMyClassBase;
    begin
      Result := TMyClassBaseClass(ClassType).Create;
      try
        Result.Assign(Self);
      except
        Result.DisposeOf;
        raise;
      end;
    end;
    

    Note that ClassType returns TClass. We cast it to TMyClassBaseClass to make sure that we call your base class virtual constructor.

    I also don't see why you made TMyClassBase<T> abstract and derived specifications from it. You should be able to implement everything you need in the generic class.