Correct way to duplicate Delphi object

34,248

Solution 1

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.

Solution 2

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.

Solution 3

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.

Share:
34,248

Related videos on Youtube

med
Author by

med

I'm a long-time Delphi programmer, writer for Monitor and Blaise Pascal magazines and a frequent contributor to the Delphi-SI community. I'm also the sole writer of the The Delphi Geek blog and proud author of the popular Delphi multithreading library - OmniThreadLibrary. Read more about me on Careers 2.0.

Updated on July 09, 2022

Comments

  • med
    med almost 2 years

    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);
    
  • splash
    splash over 13 years
    I would vote for your answer, but this would destroy your 5,555 score. ;-) Congrats!
  • Marco van de Voort
    Marco van de Voort over 13 years
    Well, I do want to reach 6666 one day :-)
  • Marco van de Voort
    Marco van de Voort over 13 years
    I in general hate using constructors with arguments. When you start making your framework more complicated, it starts biting you in the <censored>
  • mjn
    mjn over 13 years
    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).
  • splash
    splash over 13 years
    @mjustin: me too, but I usually don't use constructors for creating a copy.
  • med
    med over 13 years
    I can't make your metaclass solution work in either 2007 or XE. Can you correct your example so that it would compile?
  • med
    med over 13 years
    All clear now. (Actually, it was all clear before but I was stupidly misreading your code again and again.)
  • Marco van de Voort
    Marco van de Voort over 13 years
    The biggest problem of course being that you must establish an own root class.
  • med
    med over 13 years
    Yes, but as I stated I have complete control over that.
  • Marco van de Voort
    Marco van de Voort over 8 years
    .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.
  • Frazz
    Frazz over 8 years
    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.
  • Marco van de Voort
    Marco van de Voort over 8 years
    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.
  • Frazz
    Frazz over 8 years
    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)