Recent

Author Topic: Instantiation from Class Reference calls incorrect Constructor  (Read 1066 times)

Skotty

  • New Member
  • *
  • Posts: 13
I have the following use-case:
At run time I have a class instance I would like to clone. For this I need to create a new instance of the same type.
Using class references this should be possible:
Code: Pascal  [Select][+][-]
  1. newInstance := TClassA(originalInstance.ClassType.Create);
This produces the right class type on the new instance. So far so good.

The problem arises when I need to execute code in the constructor. Apparently when calling Create on a class reference it always calls the basic constructor of the referenced class hierarchy, instead of the constructor of the currently instanciated class.

The following example code shows the problem. When constructing b it outputs the right class name, but the TClassB constructor code is not executed. Rather it only executes the the TClassA constructor code.

Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.  {$IFDEF UNIX}
  7.   cthreads,
  8.  {$ENDIF}
  9.   Classes;
  10.  
  11. type
  12.   TClassA = class
  13.     constructor Create;
  14.   end;
  15.  
  16.   TClassB = class(TClassA)
  17.     constructor Create;
  18.   end;
  19.  
  20.   TClassARef = class of TClassA;
  21.  
  22.   constructor TClassA.Create;
  23.   begin
  24.     WriteLn('Construct A');
  25.   end;
  26.  
  27.   constructor TClassB.Create;
  28.   begin
  29.     WriteLn('Construct B');
  30.   end;
  31.  
  32. var
  33.   a: TClassA;
  34.   b: TClassA;
  35.  
  36. begin
  37.   a := TClassB.Create;
  38.   WriteLn(a.ClassName);
  39.  
  40.   b := TClassA(TClassARef(a.ClassType).Create);
  41.   WriteLn(b.ClassName);
  42. end.

Expected console output:
Quote
Construct B
TClassB
Construct B
TClassB

Actual console output:
Quote
Construct B
TClassB
Construct A
TClassB

Is this intentional behaviour, or did I miss any kind of option/parameter to set?
Obviously when there is initialization code done in a constructor, having only a base constructor be executed can cause serious negative side effects.
« Last Edit: March 28, 2020, 03:55:25 pm by Skotty »

jamie

  • Hero Member
  • *****
  • Posts: 6133
Re: Instantiation from Class Reference calls incorrect Constructor
« Reply #1 on: March 28, 2020, 03:59:50 pm »
Try putting "Virtual" on your constructors and call the inherited..etc.

constructor Create Virtual;


and in Constructor B, you need to call inherited at some point, normally first when you enter it.

also, use the override on the second one so that you actually override it.
The only true wisdom is knowing you know nothing

jamie

  • Hero Member
  • *****
  • Posts: 6133
Re: Instantiation from Class Reference calls incorrect Constructor
« Reply #2 on: March 28, 2020, 04:08:53 pm »
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.  {$IFDEF UNIX}
  7.   cthreads,
  8.  {$ENDIF}
  9.   Classes;
  10.  
  11. type
  12.   TClassA = class
  13.     constructor Create; virtual;
  14.   end;
  15.  
  16.   TClassB = class(TClassA)
  17.     constructor Create; Override;
  18.   end;
  19.  
  20.   TClassARef = class of TClassA;
  21.  
  22.   constructor TClassA.Create;
  23.   begin
  24.     WriteLn('Construct A');
  25.   end;
  26.  
  27.   constructor TClassB.Create;
  28.   begin
  29.     inherited create;
  30.     WriteLn('Construct B');
  31.   end;
  32.  
  33. var
  34.   a: TClassA;
  35.   b: TClassA;
  36.  
  37. begin
  38.   a := TClassB.Create;
  39.   WriteLn(a.ClassName);
  40.  
  41.   b := TClassA(TClassARef(a.ClassType).Create);
  42.   WriteLn(b.ClassName);
  43. end.
  44.  

Not tested , just did a quick and dirty edit...

I don't know what you are after but it looks kind of funky!
« Last Edit: March 28, 2020, 04:10:47 pm by jamie »
The only true wisdom is knowing you know nothing

lucamar

  • Hero Member
  • *****
  • Posts: 4219
Re: Instantiation from Class Reference calls incorrect Constructor
« Reply #3 on: March 28, 2020, 04:40:08 pm »
Try a little "deconstruction" and note that:

Code: [Select]
newInstance := TClassA(originalInstance.ClassType.Create);
is equivalent to:

Code: [Select]
tmp := TOriginalClass.Create;
NewInstance := TClassA(tmp);

so, naturally, the creator called is that of the original Class.

If instead you tried :

Code: [Select]
newInstance := TClassA(originalInstance.ClassType).Create;
it would be equivalent to:

Code: [Select]
newInstance := TClassA.Create;
which would just create a normal TClassA instance.

To "clone" an object you'd have to define your constructor as:

Code: Pascal  [Select][+][-]
  1. TClassA = class
  2.     constructor Create(const CloneFrom: TClassA = Nil);
  3.   end;
  4.  
  5. {...}
  6.  
  7. constructor TClassA.Create(const CloneFrom: TClassA = Nil);
  8. begin
  9.   if Assigned(CloneFrom) then
  10.     Self.Assign(CloneFrom);
  11. end;

and you'd, of course, have to program an Assign method to copy source properties, fields, etc. to Self.

To use you'd then do:
Code: Pascal  [Select][+][-]
  1. var
  2.   a: TClassA;
  3.   b: TClassB;
  4.  
  5. begin
  6.   b := TClassB.Create;
  7.   a := TClassA.Create(b);
  8. end.

Note that jamie's advice still applies: you should declare your constructors as virtual/override and call inherited from the derived classes. Otherwise the derived class constructor will "hide" (or overload) the one of the mother class.

HTH
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus/FPC 2.0.8/3.0.4 & 2.0.12/3.2.0 - 32/64 bits on:
(K|L|X)Ubuntu 12..18, Windows XP, 7, 10 and various DOSes.

Skotty

  • New Member
  • *
  • Posts: 13
Re: Instantiation from Class Reference calls incorrect Constructor
« Reply #4 on: March 28, 2020, 05:24:16 pm »
Many thanks to both of you! Indeed it was the missing virtual/override that cause it using the wrong version of the constructor. Now it gives me the right output. Also thanks for the deeper explanation. That helps me understand the why.

 

TinyPortal © 2005-2018