Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct way to duplicate Delphi object

What are pros and cons of duplication an object instance with constructor or instance function?

Example A:

type
  TMyObject = class
  strict private
    FField: integer; 
  public
    constructor Create(srcObj: TMyObject); overload; 
    //alternatively:
    //constructor CreateFrom(srcObj: TMyObject);
    property Field: integer read FField;
  end;

constructor TMyObject.Create(srcObj: TMyObject);
begin
  inherited Create;
  FField := srcObj.Field;
end;

Example B:

type
  TMyObject = class
  strict private
    FField: integer; 
  public
    function Clone: TMyObject;
    property Field: integer read FField;
  end;

function TMyObject.Clone: TMyObject;
begin
  Result := TMyObject.Create;
  Result.FField := FField;
end;

One major difference immediately springs to mind - in the latter case the Create constructor would have to be virtual so that a class hierarchy supporting Clone could be built basing on the TMyObject.

Assume that this is not a problem - that TMyObject and everything based on it is entirely under my control. What is your preferred way of doing copy constructor in Delphi? Which version do you find more readable? When would you use former or latter approach? Discuss. :)

EDIT: My main concern with the first example is that the usage is very heavy compared to the second approach, i.e.

newObj := TMyObject.Create(oldObj)

vs.

newObj := oldObj.Clone;

EDIT2 or "Why I want single-line operation"

I agree that Assign is a reasonable approach in most cases. It's even reasonable to implement 'copy constructor' internally by simply using assign.

I'm usually creating such copies when multithreading and passing objects through the message queue. If object creation is fast, I usually pass a copy of the original object because that really simplifies the issues of object ownership.

IOW, I prefer to write

Send(TMyObject.Create(obj));

or

Send(obj.Clone);

to

newObj := TMyObject.Create;
newObj.Assign(obj);
Send(newObj);
like image 321
gabr Avatar asked Oct 28 '10 10:10

gabr


3 Answers

The first adds information about which object to want to create, the second not. This can be used to instantiate e.g. a descendant or an ancestor of a class

The Delphi way (TPersistent) separates creation and cloning:

dest := TSomeClass.Create; 
dest.Assign(source);  

and has this same property that you explicitly choose the class to instantiate. But you don't need two constructors, one for normal use, and one where you want to clone.

edit due to oneline requirement You can mix it of course using Delphi metaclasses (untested)

type
  TBaseSomeObject = class;
  TBaseObjectClass = class of TBaseSomeObject;

  TBaseSomeObject = class(TPersistent)
    function Clone(t: TBaseObjectClass = nil): TBaseSomeObject; virtual;
  end;

...

  function TBaseSomeObject.Clone(t: TBaseObjectClass = nil): TBaseSomeObject;
  begin
    if Assigned(t) then
      Result := t.Create
    else
      Result := TBaseObjectClass(Self.ClassType).Create;
    Result.Assign(Self);
  end;


 SendObject(obj.Clone); // full clone.
 SendObject(obj.Clone(TDescandantObject)); // Cloned into Descendant object 

For the rest, just implement your assign() operators, and you can mix multiple ways.

edit2

I replaced the code above with code tested in D2009. There are some dependencies of the types that might have confused you, hope it is clearer this way. Of course you'll have to study the assign mechanism. I also tested the metaclass=nil default parameter and it works, so I added it.

like image 164
Marco van de Voort Avatar answered Nov 16 '22 11:11

Marco van de Voort


I don't think there is a correct way it just depend on personal style. (And as Marco pointed out, there are more ways.)

  • The constructor way is short but it violates the principle that the constructor must only construct the object. Which is possibly not a problem.
  • The clone way is short although you need to provide a call for each class.
  • The assign way is more Delphi like. It separates creation and initialization which is good because we like the one method one function concept that makes code better to maintain.

And if you implement Assign using streams, you have only one place to worry about which fields need to be available.

like image 20
Toon Krijthe Avatar answered Nov 16 '22 11:11

Toon Krijthe


I like the clone style - but only in Java (or any other GC language). I used it some times in Delphi, but mostly I stay with Create and Assign, because it is much clearer who is responsible for the destruction of the object.

like image 3
splash Avatar answered Nov 16 '22 10:11

splash