Correct way to duplicate Delphi object
Asked Answered
M

4

26

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);
Makings answered 28/10, 2010 at 10:5 Comment(0)
F
33

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.

P.s. (2024) it would be nice to see if class helpers can be used to implement this also for non root classes.

Furbish answered 28/10, 2010 at 10:24 Comment(10)
I would vote for your answer, but this would destroy your 5,555 score. ;-) Congrats!Fetial
Well, I do want to reach 6666 one day :-)Furbish
I can't make your metaclass solution work in either 2007 or XE. Can you correct your example so that it would compile?Makings
All clear now. (Actually, it was all clear before but I was stupidly misreading your code again and again.)Makings
The biggest problem of course being that you must establish an own root class.Furbish
Yes, but as I stated I have complete control over that.Makings
.The T is assigned branch is for the "cloned into descending object", in other words when the object to create is not the same as the object that contains the data. The assign() method can then make differing cases using IS to only copy common data between the objects.Furbish
Thanks. In my implementation I have also added a try/except around the Assign call. I always do that to free Result in functions that return objects. Result.Free and Raise. The Assign can and will fail if not properly implemented, and I like to avoid memory leaks.Wellbalanced
Result.free is wrong, since then the calling function can't figure out what happened. freeandnil is the way then. I guard against programming errors with unit tests, but that is the nature of my current apps. (quite critical but compact where you want to crack down on every problem you see) If you have apps with a lot of seldomly used business code your way is better to make it robust and let code just run and log errors.Furbish
I free Result and reraise the original exception (so the calling function can indeed figure it out, just like in your code). In case of exception the calling function does not get the returned value and cannot Free it (or FreeAndNil it)... thus the potential memory leak. (I have deleted my other comments, as they are of no use to anyone anymore)Wellbalanced
F
7

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.

Francesfrancesca answered 28/10, 2010 at 10:31 Comment(0)
F
4

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.

Fetial answered 28/10, 2010 at 11:8 Comment(3)
I in general hate using constructors with arguments. When you start making your framework more complicated, it starts biting you in the <censored>Furbish
I really like constructors which enforce passing all necessary objects, like Create(AOwner). Dependency injection frameworks know both flavours, Property- or Constructor based dependency injection - both have their pros (and cons).Hygrophilous
@mjustin: me too, but I usually don't use constructors for creating a copy.Fetial
B
0

I use the second method, the one with the Clone function, and it works like a charm, even with complex classes. I find it more readable and error proof.

Bonitabonito answered 16/11, 2021 at 17:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.