Recent

Author Topic: Why can't create be overridden  (Read 1630 times)

jamie

  • Hero Member
  • *****
  • Posts: 2141
Re: Why can't create be overridden
« Reply #15 on: May 17, 2019, 04:02:14 am »
The thing with non virtual methods is that you can hide them by introducing a new method of the same name
in derived classes. The nice trick is in the outside world, one can still Cast the Class instance and gain access to the previous
named method directly. This works ok as long as your methods are not virtual..

 When doing this to virtual methods, casting does not lead you that way due to the way the list is built in the background.

 
Number 1 at blue screen app creations!

Thaddy

  • Hero Member
  • *****
  • Posts: 9278
Re: Why can't create be overridden
« Reply #16 on: July 19, 2019, 11:57:45 am »
I found a way to introduce parameterless virtual create:
Code: Pascal  [Select]
  1. program properbooleanstrings;
  2. {$mode delphi}{$H+}
  3.  
  4. type
  5.   { HDC = Hide Default Constructor }
  6.   THDCObject = class
  7.   strict private
  8.   { suppress "warning constructors should be public" }
  9.   {$push}{$warn 3018 off}
  10.     constructor create;
  11.   {$pop}
  12.   end;
  13.  
  14.   TDerived = class(THDCObject)
  15.   public
  16.     constructor Create;virtual;overload;
  17.     constructor Create(const A: Integer); overload;
  18.     constructor Create(const B: string); overload;
  19.   end;
  20.  
  21.   TDerived2 = class(TDerived)
  22.   public
  23.     constructor create;override;
  24.   end;
  25.  
  26. // actually this will never get called
  27. // inherited in derived objects will call TObject.Create;
  28. constructor THDCObject.Create;
  29. begin
  30.   inherited; // calls Tobject.Create
  31.   writeln('called ', self.classname);
  32. end;
  33.  
  34. constructor TDerived.Create;
  35. begin
  36.   inherited; // Calls TObject.Create, not THDC.Create
  37.   writeln('virtual create!');
  38. end;
  39.  
  40. constructor TDerived.Create(const A: Integer);
  41. begin
  42. end;
  43.  
  44. constructor TDerived.Create(const B: string);
  45. begin
  46. end;
  47.  
  48. constructor TDerived2.Create;
  49. begin
  50.   inherited;
  51.   writeln('virtual create 2!');
  52. end;
  53.  
  54.  
  55. var
  56.   a:TDerived2;
  57. begin
  58.   { this now calls the virtual create }
  59.   a := TDerived2.Create;
  60.   a.free;
  61. end.

IAs a bonus: if you do not implement the virtual create constructor the paramterless create is now hidden. as well: prevents calling create w/o parameters.
Based on an idea by Andy Hausladen here: https://www.idefixpack.de/blog/2011/07/hiding-the-tobject-create-constructor/

I just expanded on the idea and introduced a virtual default constructor create as well.
- You can make TxxObject.Create virtual and overridable. This is usefull if you really want it to be a virtual constructor.
- You can even truely hide the parameterless create in decendent classes (as per the original link) This is very usefull if create should never be called: it will throw a compile time error.
- Code actually works and without error or warnings. It is delphi compatible too.
« Last Edit: July 19, 2019, 01:03:30 pm by Thaddy »
also related to equus asinus.

440bx

  • Hero Member
  • *****
  • Posts: 1259
Re: Why can't create be overridden
« Reply #17 on: July 20, 2019, 06:36:56 pm »
That does not explain, why it is illegal to override it...
The reason why it's illegal is very simple...

consider an object with an ancestor that has a number of fields that are "strict private", if the descendent was allowed to override the constructor, how could the descendent initialize the fields of its ancestor(s) ? ... it couldn't, because of at least three reasons, 1. it doesn't know what they are,  2. it doesn't have access to them and,   3. extension of 1. and 2., how could the descendent know how every field of every ancestor should be initialized ? (which the overriding constructor would have to know.)

IOW, a constructor is specific to the object and because of that it doesn't make sense to allow overriding it.


using FPC v3.0.4 and Lazarus 1.8.2 on Windows 7 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 9278
Re: Why can't create be overridden
« Reply #18 on: July 20, 2019, 09:02:31 pm »
The reason why it's illegal is very simple...
It is only illegal because it is public.
Other - simpler - example for the brainless  :D :D:
Code: Pascal  [Select]
  1. unit disappearing_create;
  2. {$mode delphi}
  3. interface
  4.  
  5. type
  6.   TPreDerived = class
  7.   strict private
  8.   {$push}{$warn 3018 off}
  9.     constructor Create;
  10.   {$pop}
  11.   end;
  12.  
  13. implementation
  14.  
  15.  constructor TPreDerived.Create;
  16.  begin
  17.    inherited;
  18.  end;
  19.  
  20. end.

And:
Code: Pascal  [Select]
  1. program testdisappearing;
  2. {$mode delphi}
  3. {$ifdef mswindows} {$apptype console}{$endif}
  4.  uses
  5.   disappearing_create;
  6.  
  7. type
  8.   TDerived = class(TPreDerived)
  9.   public
  10.     constructor Create(A: Integer);overload;
  11.     constructor Create(B: string);overload;
  12.   end;
  13.  
  14.   constructor Tderived.Create(A: Integer);
  15.   begin end;
  16.   constructor TDerived.Create(B: string);
  17.   begin end;
  18.  
  19. var
  20.   D: TDerived;
  21.  
  22. begin
  23.   D := TDerived.Create;  // will throw error, there is no parameter-less create in scope
  24.   D.Free;
  25. end.

As per my first example, you can now add a virtual parameter-less create too. You didn't test my code, did you?
It is
- perfectly legal once the create is hidden in an ancestor with strict private. Scope has moved to invisibility for children.
- perfectly legal to subsequently introduce a parameter-less virtual create constructor (as I already demonstrated).
- even casting won't bring it back....
- Delphi compatible
- very usefull if the parameterless create should never be called
- very useful to introduce virtual parameter-less constructors.
« Last Edit: July 20, 2019, 09:15:05 pm by Thaddy »
also related to equus asinus.

kupferstecher

  • Sr. Member
  • ****
  • Posts: 324
Re: Why can't create be overridden
« Reply #19 on: July 20, 2019, 10:42:05 pm »
The lesson in this for me was that constructors must be virtual for derived class to be able to override them.
At least for now - havent investigated the reintroduce yet.
You misunderstood.
The language feature (virtual/override) is useless for constructors, because the type is known at design time already.

Override is a special language feature, that is not obvious from its name. The very feature of override is, that the type of the variable is checked at runtime and the according method will be chosen. I.e. even if you call a method in the type of the base class, the method of the actual child class will be called. In opposite to that if you just "reintroduce" a method in the derived class, the method is called according the type of the variable and not the instance, so the method is known at design time. "reintroduce" is a keyword, but it's optional.

I think an example is neccessary here. For procedure Foo the virtual/override-feature is used, for the procedure Bar reintroduction is used.
Code: Pascal  [Select]
  1. Type TFancy= class
  2.   Procedure Foo; virtual;  //*1
  3.   Procedure Bar;       //*A
  4. end;
  5.  
  6. Type TDerivedFancy= class(TFancy)
  7.   Procedure Foo; override; //*2 (overriding the method of the base class)
  8.   Procedure Bar;  //*B (reintroducing the method of the base class)
  9. end;
  10.  
  11. var
  12.   Fancy: TFancy;
  13.   DerivedFancy: TDerivedFancy;
  14.  
  15. IMPLEMENTATION
  16.  
  17. begin
  18.   Fancy:= TFancy.Create;
  19.   DerivedFancy:= TDerivedFancy.Create;
  20.  
  21.   Fancy.Foo;  //Calls *1 (of course)
  22.   DerivedFancy.Foo; // Calls *2 (of course)
  23.   TFancy(DerivedFancy.Foo).Foo; //Calls *2 (!), because although via the typecast TFancy *1 should be called, it is determined on runtime, that its an the instance calling Foo is of type TDerivedFancy.
  24.  
  25.   Fancy.Bar //Calls *A (of course)
  26.   DerivedFancy.Bar; //Calls *B (of course)
  27.   TFancy(DerivedFancy).Bar; //Calls *A (!), because of the typecast TFancy.
  28.  
  29. end;
The reintroduce mechanism is the "cheap" one, the compiler checks what type is the variable and the according method will be called, even if there is a derived type instance in the variable. The override mechanism needs a runtime check, i.e. extra computation time and memory, to see which instance, of base or derived type is in the variable.

For a constructor I can't see a situation, where you don't know at design time which type the constructor has, as you have to actually define the type when you call the constructor. Thus you use the reintroduce mechanism and not override.


PascalDragon

  • Hero Member
  • *****
  • Posts: 705
  • Compiler Developer
Re: Why can't create be overridden
« Reply #20 on: July 21, 2019, 10:16:12 am »
The language feature (virtual/override) is useless for constructors, because the type is known at design time already.
You are wrong as Object Pascal supports meta classes:

Code: Pascal  [Select]
  1. type
  2.   TMyObject = class
  3.     constructor Create(aSomeArg: LongInt); virtual;
  4.   end;
  5.   TMyClass = class of TMyObject
  6.  
  7.   TMyObject1 = class(TMyObject)
  8.     constructor Create(aSomeArg: LongInt); override;
  9.   end;
  10.  
  11.   TMyObject2 = class(TMyObject)
  12.     constructor Create(aSomeArg: LongInt); override;
  13.   end;
  14.  
  15. { skipping implementations of the constructors }
  16.  
  17. var
  18.   c: TMyClass;
  19.   o: TMyObject;
  20. begin
  21.   c := TMyObject1;
  22.   o := c.Create(42); // will call TMyObject1.Create
  23.   c := TMyObject2;
  24.   o := c.Create(42); // will call TMyObject2.Create
  25. end.
This is extensively used in various class libraries (e.g. the LCL).

kupferstecher

  • Sr. Member
  • ****
  • Posts: 324
Re: Why can't create be overridden
« Reply #21 on: July 21, 2019, 08:31:50 pm »
You are wrong as Object Pascal supports meta classes
Thanks for pointing that out and especially fo the example! Meta classes were new to me.
« Last Edit: July 22, 2019, 10:22:18 am by kupferstecher »

PascalDragon

  • Hero Member
  • *****
  • Posts: 705
  • Compiler Developer
Re: Why can't create be overridden
« Reply #22 on: July 22, 2019, 09:18:31 am »
You are wrong as Object Pascal supports meta classes
Thanks for pointing that out and especially fo the example! Meta classes were new to me.
It's also documented here as "Class reference type" (around the middle of the page)