Re: OO problem: How to create a duplicate of a derived class with only a base variable ?

From: Skybuck Flying (nospam_at_hotmail.com)
Date: 11/21/04


Date: Sun, 21 Nov 2004 00:55:23 +0100


"Maarten Wiltink" <maarten@kittensandcats.net> wrote in message
news:419e1005$0$568$e4fe514c@news.xs4all.nl...
> "Skybuck Flying" <nospam@hotmail.com> wrote in message
> news:cnjle6$p12$1@news3.zwoll1.ov.home.nl...
> [...]
> > At first [making copies] seemed handy but now I might have memory leaks.
>
> > TBaseElement = class;
> > TBaseString = class(TBaseElement);
> > TBaseInteger = class(TBaseElement);
> >
> > var
> > BaseElement : TBaseElement;
> >
> > // create list
> > BaseElement := TBaseList.Create;
> >
> > // add some elements
> > TBaseList(BaseElement).Element[0] := TBaseString.Create;
>
> TBaseString.Create returns a reference to a newly allocated object. You
> proceed to make a copy of this object, add a reference to the copy to
> your list, and to lose track of the original object. Yes, that's a
> memory leak.
>
> If you had bothered to acquaint yourself with the wisdom of generations
> past, instead of inventing complete bogus sciences at every step, you
> might have known about _ownership_ of references. There may be many
> references (pointers) to a single object, but only one owns it. (Non-
> owning references are termed "aliases".)
>
> Creating an object gives you the first reference to it, so it must be
> the owning one. It depends on the semantics of a list if storing a
> reference in it transfers ownership, but whichever is the case, it
> should be documented and documented well. Note that if a list is
> documented to take ownership, you must not try to store an alias in it.
> And this is not something that can be determined from the pointer; you
> must _know_ that you own the reference which you are now giving to the
> list.
>
> A list that stores only references to copies of objects obviously does
> not even try to take ownership. This _proves_ that in the above code,
> it was up to you to free the created objects after adding them to the
> list.
>
>
> > In Java it would simply be garbage collected.
>
> Correct. You've lost all references to it.
>
>
> > *sigh* But will delphi clean it up automatically ???
> >
> > Probably not.. since Delphi says classess are nothing but pointers.
>
> Delphi classes are simply not garbage-collected in any way.
>
>
> > The confusing part however is that Delphi also takes about "class
> > references" and "reference counted strings"
> >
> > So I can see how some programmer might start thinking that "class
> > references" are "reference counted" as well.
>
> Show me where it says that all references are counted. Some of them
> are; that is then documented explicitly. Long strings, dynamic arrays,
> interface references. Even then it's easy to keep the reference counting
> but disable garbage collection for interface references.
>
>
> > I find it intuitive to start thinking like that... but nooooo Delphi
> > does not do that. Classess are not reference counted last time I
> > checked... that means no auto-clean up.... the kinda weird thing is
> > that a dynamic array of class reference *is* auto cleaned up and all
> > classess/objects in it as well!
>
> The array itself is garbage-collected, but its contents are simply
> thrown away.
>
>
> [...]
> > From the above it seems I don't have to make a copy at all. UNLESS ?!
> > I want to do this:
> >
> > var
> > KeepBaseStringAlive : TBaseString;
> >
> >
> > KeepBaseStringAlive := TBaseString.Create;
> >
> > TBaseList(BaseElement).Element[0] := KeepBaseStringAlive;
> >
> > KeepBaseStringAlive.Destroy;
> >
> > Do you see the problem "developing" here ?!
>
> Yes. You're not allowed to do that. If you have changed the semantics
> of the list to take ownership of any object references in it, you may
> no longer free any objects that are in the list.
>
>
> [...]
> > *Sigh*... so I guess it's up to the user of the list/container to make
> > sure that the objects that are put into it stay alive... and trust the
> > list to clean up instead ;)
>
> We do not trust. We read the source and make sure.
>
>
> [...]
> > So if I would need to duplicate objects in the future for some reason I
> > might have a problem there and then... and still need the duplicate
> > functions...
> >
> > Then I might end up writing something like:
> >
> > TBaseList(BaseElement).Element[0] := KeepBaseStringAlive.Duplicate;
> > // then it would return a duplicate of itself ;)
> >
> > That's not so bad :)
>
> No. And it gives very good hints about what goes on. This must be
> a list that takes ownership.
>
>
> [...]
> >> Make TBase.Create virtual. ...
>
> > Hmm.. I don't understand this yet... why does it have to be virtual ?
> >
> > Isn't the "deepest" child's constructor always called ?
>
> At some point, you're going to call Create on a class reference that the
> compiler knows only to be a TBaseClass. If Create were static, the
> Create in TBaseClass would be called. If Create is virtual, the latest
> override will be called instead.
>
> var
> CompClass: TComponentClass;
> NewComp: TComponent;
>
> CompClass:=TButton;
> NewComp:=CompClass.Create(Owner);
>
> This is the situation I mean. The compiler knows that CompClass is the
> class of TComponent, or any class derived from TComponent. At run-time,
> the program knows it's actually TButton. TComponent, for the very same
> reasons as yours, has a virtual contructor. TButton.Create will be
> called.
>
> The converse of "virtual" is "static". That's not a random name, it
> refers to the fact that for static methods, the compiler will select
> the one to call at compile time. It would do so based on what it knows,
> and it knows only the declared type of CompClass. So you would not end
> up with a TButton, but with a TComponent object. Not what you want.
>
> [...]
> > Well I read delphi's help (again) and for the moment I think I see
> > what the use of (class of...) is for...
>
> Some days I actually believe that there is hope for you.
>
>
> > It's a way of passing "types" to parameters etc...
> >
> > And then later one can simply do... Parameter.Create... and it
> > automatically knows what type to create...
>
> Some days, that feeling even lasts beyond your next sentence.
>
>
> > [...] it seems delphi classess already have a ClassType function ;)
>
> There is. But it's declared to return a TClass (class of TObject) value,
> and TObject has a static constructor. If CompClass had been declared
> TClass above, the last line would always call TObject.Create.
>
>
> > For a second I thought this was the solution but nope... this returns
> > simply the variable's type... and not the derived type etc.
>
> Go back and check. It's always worked for me. Put a TButton in a TObject
> variable and ask it for its ClassType. It should say TButton, not
> TObject.
>
>
> > Delphi help says: "is" operators on the dynamic types at runtime...
> > apperently the '=' operator does not... hehe.
>
> "is" supports polymorphism. "=" does not. A TButton IsA (is) TComponent,
> but its class type is TButton which does not equal (=) TComponent.
>
> Class references are pointers, too, and "class of TComponent" points
> somewhere else than "class of TButton". You can check that
> TButton.InheritsFrom(TComponent), but pointer equality is a different
> question.
>
>
> [...]
> >> class function TBase.ClassType: TBaseClass;
> >> begin
> >> Result:=(inherited ClassType) as TBase(Class?);
> >> end;
> >
> > Euhm... this example incomplete and weird... :)
> >
> > Why would you want the inherited classtype...
>
> Because ClassType returns the "real" classtype, but as a TClass value.
> I _know_ it's at least TBase, possibly a class derived from it, and
> to use TBase's virtual constructor or any override, I need to tell the
> compiler that.
>
> If you call this class method on an object, it will return its actual
> classtype, and you can call Create on that. The compiler, knowing that
> the classtype is TBase "or better", will call TBase's Create. Which is
> virtual, so you get the latest override for the object you started with.
>
> Again, TObject.ClassType is declared to return a TClass value, and
> TClass is the class of TObject, and TObject has a static constructor.
> So calling Create on the return value of TObject.ClassType will call
> TObject.Create, not any derived class's constructor, and you will not
> be able to make a good copy.

This is not true.

ClassType returns the type of the reference... the derived type...

>
>
> >> The hard part is definitely writing all the Assign[To] methods. Once
> >> you have the virtual constructor, most of the rest should fall into
> >> place.
> >
> > Well Assign is simply a member wise copy... that doesn't seem to hard.
>
> It can be a _lot_ of typing even when you _are_ smart about it.
>
>
> > Sigh...
>
> Nobody promised it would be easy. Okay, they did, but they lied.
>
>
> > Well both designs have their ups and downs.
> >
> > the "copy" solution lead to a memory leak (partially because delphi
> > does not auto clean up out of scope objects) and is a lot slower
> > probably.
>
> The memory leak is your own responsibility and is easily cured when you
> keep track of who is responsible for each object. Copying takes time so
> it will be slower. Not that you will notice.
>
>
> > (maybe that's why java is a lot slower :)
>
> No, that has different reasons. Garbage collection is one of them, it
> takes time, too.
>
>
> > I am not sure if java has a copy operator or something ...
>
> It's even named Copy(). In C#, it's called MemberwiseClone(). This is
> progress.
>
>
> [...]
> > The problem that remains is figuring out if their is a way to learn
> > the "derived type" that is inside a "base type variable"
>
> ClassType. Really.

Yes now you contradict yourself hehe.

>
>
> > The "is" operator will return the "deepest" child... (= derived type)
>
> No, it will _match_ the actual classtype, as well as any of its ancestor
> classes.
>
>
> [...]
> > The drawback of the "is" operator is that it is needed for to check
> > against every possibly derived type.
>
> Where you use it, yes. But it's not necessary to use it in many of the
> cases you think.
>
> When I implement Assign, or AssignTo, they tend to check a number of
> classes one after another, using is.
>
>
> > For example:
> >
> > // make a copy of a.
> > if A is TDerived1 then B := TDerived1.Create;
> > if A is TDerived2 then B := TDerived2.Create;
> > if A is TDerived3 then B := TDerived3.Create;
> >
> > // etc...
> >
> > Ok this problem is easily solved with
> >
> > B := A.duplicate;
> >
> > But that's because the method duplicate knows what type it belongs
> > too...
>
> Yes. But this is dispatched automatically, not checked against a (long)
> list. And when you add TDerived4, you don't have to go back and change
> all the existing code, you only have to make TDerived4 capable of
> assigning itself to all other currently known classes.
>
> Duplicate could be introduced in TBase, using ClassType and the virtual
> Create, and leave the hard work to Assign, which will in turn leave it
> to AssignTo.
>
> Imagine a class hierarchy as follows:
>
> TBase
> |
> +- TDerived
> |
> +- TLeaf
>
> type
> TBase = class(TObject)
> protected
> procedure AssignTo(Dest: TPersistent); override;
> procedure AssignToBase(const Dest: TBase); virtual;
> public
> FOne: Integer;
> constructor Create; virtual;
> class function ClassType: TBaseClass;
> class function Duplicate: TBase;
> end;
>
> TDerived = class(TBase)
> protected
> procedure AssignTo(Dest: TPersistent); override;
> procedure AssignToDerived(const Dest: TDerived); virtual;
> public
> FTwo: string;
> constructor Create; override;
> end;
>
> TLeaf = class(TDerived)
> protected
> procedure AssignTo(Dest: TPersistent); override;
> procedure AssignToLeaf(const Dest: TLeaf); virtual;
> public
> FThree: Real;
> constructor Create; override;
> end;
>
> constructor TBase.Create;
> begin
> inherited Create;
> FOne:=(-1);
> end;
>
> class function TBase.ClassType: TBaseClass;
> begin
> Result:=(inherited ClassType) as TBase; { Or TBaseClass, I don't know }
> end;
>
> class function TBase.Duplicate: TBase;
> begin
> Result:=ClassType.Create;
> Result.Assign(Self);
> end;
>
> procedure TBase.AssignTo(Dest: TPersistent);
> begin
> if (Dest is TBase)
> then AssignToBase(Dest as TBase)
> else inherited AssignTo(Dest);
> end;
>
> procedure TBase.AssignToBase(const Dest: TBase);
> begin
> inherited AssignTo(Dest);
> Dest.FOne:=Self.FOne;
> end;
>
> constructor TDerived.Create;
> begin
> inherited Create;
> FTwo:='';
> end;
>
> procedure TDerived.AssignTo(Dest: TPersistent);
> begin
> if (Dest is TDerived)
> then AssignToDerived(Dest as TDerived)
> else inherited AssignTo(Dest);
> end;
>
> procedure TDerived.AssignToDerived(const Dest: TDerived);
> begin
> inherited AssignTo(Dest);
> Dest.FTwo:=Self.FTwo;
> end;

unit Unit1;

interface

uses
  Classes;

type
 TBaseClass = class of Tbase;

   TBase = class(TPersistent)
   protected
  procedure AssignTo(Dest: TPersistent); override;
  procedure AssignToBase(const Dest: TBase); virtual;
   public
  FOne: Integer;
  constructor Create; virtual;
  class function ClassType: TBaseClass;
  class function Duplicate: TBase;
   end;

   TDerived = class(TBase)
   protected
  procedure AssignTo(Dest: TPersistent); override;
 procedure AssignToDerived(const Dest: TDerived); virtual;
   public
  FTwo: string;
  constructor Create; override;
   end;

   TLeaf = class(TDerived)
   protected
  procedure AssignTo(Dest: TPersistent); override;
  procedure AssignToLeaf(const Dest: TLeaf); virtual;
   public
  FThree: Real;
  constructor Create; override;
   end;

implementation
 constructor TBase.Create;
 begin
   inherited Create;
   FOne:=(-1);
 end;

 class function TBase.ClassType: TBaseClass;
 begin
 // stops compiling here
   Result:=(inherited ClassType) as TBase; { Or TBaseClass, I don't know }
 end;

 class function TBase.Duplicate: TBase;
 begin
   Result:=ClassType.Create;
   Result.Assign(Self);
 end;

 procedure TBase.AssignTo(Dest: TPersistent);
 begin
   if (Dest is TBase)
   then AssignToBase(Dest as TBase)
   else inherited AssignTo(Dest);
 end;

 procedure TBase.AssignToBase(const Dest: TBase);
 begin
   inherited AssignTo(Dest);
   Dest.FOne:=Self.FOne;
 end;

 constructor TDerived.Create;
 begin
   inherited Create;
   FTwo:='';
 end;

 procedure TDerived.AssignTo(Dest: TPersistent);
 begin
   if (Dest is TDerived)
   then AssignToDerived(Dest as TDerived)
   else inherited AssignTo(Dest);
 end;

 procedure TDerived.AssignToDerived(const Dest: TDerived);
 begin
   inherited AssignTo(Dest);
   Dest.FTwo:=Self.FTwo;
 end;

end.

>
> TLeaf follows the same pattern as TDerived. This code should compile,
> or almost. Its untested but I've done this before and it should be
> correct.
>
> From another unit, try to allocate objects of all three types, set
> their fields, and have them copy themselves. See if you can step
> through the entire process and if it works. If it doesn't, I'll fix
> it. That's a promise. I've done this before.
>
>
> > [...] delphi does not have any "duplicate" functions...
> > It only has a "create" functionality... and that's not quite the same.
>
> It's not at all the same. It has a different function: it gives you a
> _new_ object.
>
>
> > It does have a "assign" functionality... but that's also not quite the
> > same. ( well not really you have to make it yourself :) )
>
> Yes. It would be possible to enumerate all published properties and copy
> them over, but it would be wrong to have TPersistent do that. You can
> build such a function and call it from your own classes' Assign overrides.
>
>
> > So combining these would give a duplicate function.
>
> Yes. See above.
>
>
> > So only one question remains: Is it enough to write a "base"
> > duplication function. The answer is probably "NO !" Because the base
> > class does not know about the properties/fields of the "derived
> > classess". In short: The base classess simply do not know how to
> > "duplicate" the derived classess. Only the derived classess now that.
> > So that means the derived classess "have" to have a assign and
> > optionality a duplicate function for easy use.
>
> Almost completely right (and this is _not_ an easy subject). Duplication
> delegates to (Assign, which defers to) AssignTo, which is the only part
> that needs to be done anew for each new class. But only for the things
> that are added in that class.

As I presented in my other post I already have my * ultimate * duplication
function.

And it's not a class method.

The only thing that remains.. is if it is possible to do the same with a
class method duplication function..

I d-o-n-'-t t-h-i-n-k so :)

>
>
> [...]
> > Wow... [...] it is possible for a base class to call the "derived"
> > versions if it really is a derived type
>
> The language feature "override" actually works. What fun.
>
>
> [...]
> > But then the design is less free... it has to be the same...
>
> Yes. Once you make a method virtual, you can override it only with the
> same parameters. "This is the price you pay."
>
>
> > So using these "class of class type" types might just be an alternative
> > implementation without any benefits ? since I would estimate the same
> > ammount of code is needed. (For example... every derived type would need
> > a "Type function" to return it's "ClassType" )
>
> No. You need it to get at the virtual constructor; only the class where
> Create becomes virtual needs it.

Bye,
  Skybuck.


Quantcast