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