Lazarus

Free Pascal => Beginners => Topic started by: Birger52 on May 11, 2019, 02:33:04 pm

Title: Why can't create be overridden
Post by: Birger52 on May 11, 2019, 02:33:04 pm
I'm thinking there must be someting I have my head around the wrong way.
Creating a class of my own.

Code: Pascal  [Select][+][-]
  1. type
  2.   TMyThumbData = class(TObject)
  3.   private
  4.  ...
  5.   public
  6.      constructor Create; override;
  7. ...
  8.  

I get a compiler error:
Quote
mythumbdata.pas(17,18) Error: There is no method in an ancestor class to be overridden: "constructor Create;"

So I look up TObject (https://www.freepascal.org/docs-html/rtl/system/tobject.html)
Code: Pascal  [Select][+][-]
  1. type TObject = class
  2. public
  3.   constructor Create;
  4. ...

So is the documentation or the compiler right?
I have to remove the override - compiler insists it is en error, and the program will not run with it, so I guess compiler wins the contest.

In the create method of my new class
Code: Pascal  [Select][+][-]
  1. constructor TMyThumbData.Create;
  2. begin
  3.   inherited Create;
  4. ...
  5.  
There is no error or warning - so the Create that can not be overriden do exist...

I know, it is only TObject, so it doesn't really matter (TObject.Create does nothing).
An maybe I am wearing both belt and suspenders - but I don't see doing anything "errorneous".

In fact, the only "error" I can find in this, is that the compiler, will not let me override TObject.Create
Or is there some reason that it for TObject is not allowed, or other situations it is not allowed?
Title: Re: Why can't create be overridden
Post by: korba812 on May 11, 2019, 03:10:55 pm
from https://www.freepascal.org/docs-html/rtl/system/tobject.create.html (https://www.freepascal.org/docs-html/rtl/system/tobject.create.html):

Create creates a new instance of TObject. Currently it does nothing. It is also not virtual, so there is in principle no need to call it directly.
Title: Re: Why can't create be overridden
Post by: Birger52 on May 11, 2019, 03:17:59 pm
That does not explain, why it is illegal to override it...
Title: Re: Why can't create be overridden
Post by: korba812 on May 11, 2019, 03:25:20 pm
Yes, it is. TObject.Create is not virtual. Only virtual methods can be overridden.

https://www.freepascal.org/docs-html/ref/refsu27.html (https://www.freepascal.org/docs-html/ref/refsu27.html)
Title: Re: Why can't create be overridden
Post by: Martin_fr on May 11, 2019, 04:12:20 pm
Calling "inherited" and using "override" are independent things.

Calling "inherited" basically means: From the base class.
You can call any method of the base-class (or the base-class' base(s)) using inherited.
Code: Pascal  [Select][+][-]
  1. Procedure TMyClass.Foo;
  2. begin
  3.   inherited Bar; // fine as long as there is a Bar in the inheritance
  4. end;
  5.  

Usually you use "inherited", if your class "hides" a method in the base class.
You can hide it by
- Just declaring it (no override). That will give a warning, and might not behave as you expect. But you can do it.
- "override" it. If the parent is "virtual"
- "reintroduce" it (google it, if interested)

Normally a constructor you just declare it. (There are virtual constructors, but that is only if you use "class of TMyClass" - different topic - do not worry about it for now)

So using "inherited" does not require virtual and override.
Title: Re: Why can't create be overridden
Post by: Birger52 on May 11, 2019, 05:45:24 pm
OK Thx.

Didn't know only virtual methods could be overwritten.
Title: Re: Why can't create be overridden
Post by: devEric69 on May 14, 2019, 01:31:05 pm
Quote
"reintroduce" it (google it, if interested)

nb: in addition to information, with a "reintroduce; overload;" method, you have to think about setting the property "OldCreateOrder:= true;", if we want to create a custom constructor of a TForm or a TDatamodule, for example.
Title: Re: Why can't create be overridden
Post by: Thaddy on May 14, 2019, 04:03:31 pm
Quote
"reintroduce" it (google it, if interested)

nb: in addition to information, with a "reintroduce; overload;" method, you have to think about setting the property "OldCreateOrder:= true;", if we want to create a custom constructor of a TForm or a TDatamodule, for example.
I suggest to delete that because it is nonsense...
Title: Re: Why can't create be overridden
Post by: devEric69 on May 14, 2019, 06:26:00 pm
Indeed.
I haven't been precise enough: my remark is in the context, where we hide one or more components by moving them into the private section of a container like TForm, TDatamodule, TFrame, ..., then we search them in a custom "reintroduce; overload;" constructor with findComponent(['foo1']) as TFoo; and registerClasses([TFoo, ...]); in the initialization unit's part.
Title: Re: Why can't create be overridden
Post by: VTwin on May 15, 2019, 01:55:31 am
Usually you use "inherited", if your class "hides" a method in the base class.
You can hide it by
- Just declaring it (no override). That will give a warning, and might not behave as you expect. But you can do it.

Normally a constructor you just declare it.

I don't follow. It is normal to declare it without override, but it might not behave as you expect? That sounds contradictory. Could you clarify?
Title: Re: Why can't create be overridden
Post by: Martin_fr on May 15, 2019, 02:32:46 am
Usually you use "inherited", if your class "hides" a method in the base class.
You can hide it by
- Just declaring it (no override). That will give a warning, and might not behave as you expect. But you can do it.

Normally a constructor you just declare it.

I don't follow. It is normal to declare it without override, but it might not behave as you expect? That sounds contradictory. Could you clarify?

The green part was about normal methods. Only the last (black) line referred to constructors.
I explained this part on normal methods, because virtual constructors is an advanced topic, which I did not want to introduce then.

While the topic is about constructors, it also is about the use of "inherited".
Probably "inherited" in this case was observed by the OT as part of the code created for the constructor (by codetools).
But in tutorials "inherited" is often introduced together with virtual/override. Which would explain why the OT wondered about the constructor not using virtual/override, when at the other hand it does use "inherited".

I pointed out that in a normal method you can use "inherited" without virtual/override too.
But "reintroducing" a normal method instead of overriding it, may not be what the OT may expect. (That of course is me guessing, I have no real knowledge about the expectations of the OT)
Title: Re: Why can't create be overridden
Post by: VTwin on May 15, 2019, 03:25:40 am
Martin_fr,

Got it. I did not follow the distinction you made between normal methods and constructors. I see that now. I have not played with "reintroducing', and am not inclined to. I have not always been clear on the best practices for declaring constructors (virtual or not). Thanks for the clarification.

Cheers,
VTwin
Title: Re: Why can't create be overridden
Post by: devEric69 on May 15, 2019, 09:54:10 am
@Thaddy and @VTwin: everything Martin_fr says are the basics, and cover 95% of programming cases.
For those who are curious and wondering what "reintroduce" is for, you can read this (Marco Cantu's article entitled "20 rules for OOP In Delphi", and for Lazarus too, so).
This article can be easily found on the web, for example here:
http://zeus.nyf.hu/~bajalinov/my_special/SW/Delphi%20eBooks/Delphi/Delphi%20-%2020%20Rules%20for%20OOP%20in%20Delphi.pdf (http://zeus.nyf.hu/~bajalinov/my_special/SW/Delphi%20eBooks/Delphi/Delphi%20-%2020%20Rules%20for%20OOP%20in%20Delphi.pdf) .
Title: Re: Why can't create be overridden
Post by: marcov on May 15, 2019, 10:21:07 am
Just for fun: if you start using virtual constructors you'll often use "class of" types.

Checking if a "class of x" typed variable inherits from class y has a famous gotcha, you need to use x.inheritsfrom(y), not IS which won't compile or not work properly.

Code: [Select]
{$mode delphi}
type   tx = class
           end;
       txclass = class of tx;
       ty = class(tx)
            y:integer;
          end;
       tyy = class(ty)
            y2:integer;
          end;

       tz = class(tx)
            y:integer;
          end;
         
 
var x : txclass;

begin
  x:=ty;
 { if x is ty then               // won't compile
    writeln('x is ty');         
  if x is tz then
    writeln('x is tz');}
   if x.inheritsfrom(ty) then
    writeln('x inheritsfrom ty');         
   if x.inheritsfrom(tz) then
    writeln('x inheritsfrom tz');
end.
Title: Re: Why can't create be overridden
Post by: Birger52 on May 17, 2019, 02:33:06 am
Martin_fr
Quote
Which would explain why the OT wondered about the constructor not using virtual/override, when at the other hand it does use "inherited".
Precisely. It is there, but I'm not allowed to override it.
I did know, I didn't have to, as it does nothing. But still (re)learning and reintroducing myself. Many new things since the beginning.
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.

Title: Re: Why can't create be overridden
Post by: jamie 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.

 
Title: Re: Why can't create be overridden
Post by: Thaddy 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.
Title: Re: Why can't create be overridden
Post by: 440bx 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.


Title: Re: Why can't create be overridden
Post by: Thaddy 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.
Title: Re: Why can't create be overridden
Post by: kupferstecher 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.

Title: Re: Why can't create be overridden
Post by: PascalDragon 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).
Title: Re: Why can't create be overridden
Post by: kupferstecher 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.
Title: Re: Why can't create be overridden
Post by: PascalDragon 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 (https://www.freepascal.org/docs-html/current/ref/refse34.html#x69-910006.1) as "Class reference type" (around the middle of the page)
TinyPortal © 2005-2018