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