Recent

Author Topic: Please clarify the use of "virtual"  (Read 4338 times)

ArminLinder

  • Sr. Member
  • ****
  • Posts: 320
  • Keep it simple.
Please clarify the use of "virtual"
« on: September 03, 2020, 06:43:40 pm »
I am referring to the docs at

https://wiki.freepascal.org/Class#virtual.2C_dynamic.2C_override

There it says:

virtual means that the method can be overwritten by the derived class

IMHO methods not marked "virtual" can also be overwritten. So overridability does not seem a reason to use "virtual". And:

A derived class can implement its own version of a virtual method

IMHO a derived class can implement it's own version of any method, except "static" ones, no matter whether it was marked "virtual" in the parent class or not. And:

if the new method is marked override it hides the base virtual method

which means exactly what? A new method with the same name does already replace the method of the parent class, what else could I need to do to it to "hide" it?

Confused. What is that override, virtual and dynamic stuff all about? Who can clarify?

Thnx, Armin.

« Last Edit: September 03, 2020, 06:46:48 pm by Nimral »
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

Thaddy

  • Hero Member
  • *****
  • Posts: 18934
  • Glad to be alive.
Re: Please clarify the use of "virtual"
« Reply #1 on: September 03, 2020, 07:10:58 pm »
Override does not hide a method, It replaces it.
Recovered from removal of tumor in tongue following tongue reconstruction with a part from my leg.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12291
  • Debugger - SynEdit - and more
    • wiki
Re: Please clarify the use of "virtual"
« Reply #2 on: September 03, 2020, 07:25:33 pm »
Sometimes you have a variable (for example parameter to a function), that accepts a base class.

Code: Pascal  [Select][+][-]
  1. Function Describe(v: TVehicle): String;
  2. begin
  3.   result := v.Foo;
  4. end;
  5.  
You may pass in a TCar, or a TBicycle.

Quote
type TVehicle = class
  function Foo: String;
end;

If TCar would impelment Foo too, then Describe still calls TVehicle.Foo.

If Foo was virtual, then Describe could see the overwritten Foo.

wp

  • Hero Member
  • *****
  • Posts: 13484
Re: Please clarify the use of "virtual"
« Reply #3 on: September 03, 2020, 07:27:05 pm »
Suppose a hierarchy of geometric shapes: TTriangle, TRectangle, TCircle. All of them inherit from the basic TGeometricShape class which is supposed to have a method "Draw". Each shape implements its own drawing method according to its name.

Now suppose the user draws some shapes and stores them in an array GeometricShapes of TGeometricShape. When he wants to draw all shapes he iterates through this array and calls Draw for each shape. Without virtual methods the compiler would not know at compile time which Draw method should be called and would call the one implemented by TGeometricShape (which probably does nothing) because this is the class used in the declaration. In order to call the correct Draw method the code should have to check the type of the instance, type-cast the array element to the correct class and call its Draw method:

Code: Pascal  [Select][+][-]
  1. var
  2.   shape: TGeometricShape;
  3. begin
  4.   for shape in GeometricShapeArray do
  5.     if (shape is TCircle) then
  6.       TCircle(shape).Draw
  7.     else if (shape is TRectangle) then
  8.       TRectangle(shape).Draw
  9.     else if (shape is TTriangle) then
  10.       TTriangle(shape).Draw;
  11.   end;

which is a mess because this code needs to know about all possible shapes.

But when the TGeometricShape.Draw is declared as "virtual" (and each one inherited and overridden by the descendants as "override") then the compiler builds a "virtual method table" (VMT) which contains the addresses of the Draw methods to be executed actually at runtime. When now the user iterates through the array elements the Draw method used is the one in the VMT, and this is the correct one for each instance of the geometric shapes.

Code: Pascal  [Select][+][-]
  1. var
  2.   shape: TGeometricShape;
  3. begin
  4.   for shape in GeometricShapes do
  5.   shape.Draw;  // The correct Draw method will be used for each class

If in this case there is a need for a TStarShape later, then this new shape can be implemented in its own unit with its own Draw method (now marked as "overide"), and it is no longer needed to modify the code for drawing the shape array (which maybe is in a third-party library).

« Last Edit: September 03, 2020, 07:32:55 pm by wp »

howardpc

  • Hero Member
  • *****
  • Posts: 4144
Re: Please clarify the use of "virtual"
« Reply #4 on: September 03, 2020, 07:30:45 pm »
Perhaps this code helps.
Code: Pascal  [Select][+][-]
  1. program VirtualExamples;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. type
  6.   TBase = class(TObject)
  7.   public
  8.     function NonVirtualGoodbye: String;
  9.     function VirtualGoodbye: String; virtual;
  10.   end;
  11.  
  12.   TGermanBase = class(TBase)
  13.   public
  14.     function NonVirtualGoodbye: String;
  15.     function VirtualGoodbye: String; override;
  16.   end;
  17.  
  18. { TGermanBase }
  19.  
  20. function TGermanBase.NonVirtualGoodbye: String;
  21. begin
  22.   Exit('aus sein');
  23. end;
  24.  
  25. function TGermanBase.VirtualGoodbye: String;
  26. begin
  27.   Result := inherited VirtualGoodbye + ' + Auf Wiedersehen';
  28. end;
  29.  
  30. { TBase }
  31.  
  32. function TBase.NonVirtualGoodbye: String;
  33. begin
  34.   Exit('Goodbye (TBase non-virtual)');
  35. end;
  36.  
  37. function TBase.VirtualGoodbye: String;
  38. begin
  39.   Exit('Goodbye (TBase virtual)');
  40. end;
  41.  
  42. var
  43.   b: TBase;
  44.   g: TGermanBase;
  45. begin
  46.   b := TBase.Create;
  47.   WriteLn('Create b as TBase:');
  48.   WriteLn('b.NonVirtualGoodbye: ',b.NonVirtualGoodbye);
  49.   WriteLn('b.VirtualGoodbye: ',b.VirtualGoodbye);
  50.  
  51.   WriteLn(#10'Create b as TGermanBase, and repeat:');
  52.   b.Free;
  53.   b := TGermanBase.Create;
  54.   WriteLn('b.NonVirtualGoodbye: ',b.NonVirtualGoodbye);
  55.   WriteLn('b.VirtualGoodbye: ',b.VirtualGoodbye);
  56.   b.Free;
  57.  
  58.   WriteLn(#10'Create g as TGermanBase, and repeat:');
  59.   g := TGermanBase.Create;
  60.   WriteLn('g.NonVirtualGoodbye: ',g.NonVirtualGoodbye);
  61.   WriteLn('g.VirtualGoodbye: ',g.VirtualGoodbye);
  62.   g.Free;
  63.  
  64.   ReadLn;
  65. end.
The output is
Code: Pascal  [Select][+][-]
  1. Create b as TBase:
  2. b.NonVirtualGoodbye: Goodbye (TBase non-virtual)
  3. b.VirtualGoodbye: Goodbye (TBase virtual)
  4.  
  5. Create b as TGermanBase, and repeat:
  6. b.NonVirtualGoodbye: Goodbye (TBase non-virtual)
  7. b.VirtualGoodbye: Goodbye (TBase virtual) + Auf Wiedersehen
  8.  
  9. Create g as TGermanBase, and repeat:
  10. g.NonVirtualGoodbye: aus sein
  11. g.VirtualGoodbye: Goodbye (TBase virtual) + Auf Wiedersehen


jamie

  • Hero Member
  • *****
  • Posts: 7653
Re: Please clarify the use of "virtual"
« Reply #5 on: September 03, 2020, 07:44:08 pm »
Quote

 if the new method is marked override it hides the base virtual method

which means exactly what? A new method with the same name does already replace the method of the parent class, what else could I need to do to it to "hide" it?

Confused. What is that override, virtual and dynamic stuff all about? Who can clarify?


 For a short explanation

   Think of the Virtual methods as a pointer list to methods, each time you derive a new class from the last one and specify OVRRIDE on a virtual method it adds it to the top of the list of pointers.
 
   The idea is that no matter where in the class this method is called it will always call the first top one, the last derived class that override it .

   The general rule is that you first Call the "Inherited" so that all ancestor implementations of that Method first get executed, but you don't have to that, you can make modifications to the parameters if any before allowing ancestor methods to be executed.

 Dynamic is and old hang over from the Delphi days, its basically the same thing but Delphi has a different way to implement it for efficiency in early OS. We don't worry so much about that any more ;)

 And as for the hiding, yes you can re-introduce a method of the same name and it can hide the ancestor if you don't call the inherited feature.. 
 
 No Virtual methods do not get called at the top of the last derived entity , they just call the local implementation of where ever you are in the class.

The only true wisdom is knowing you know nothing

Handoko

  • Hero Member
  • *****
  • Posts: 5526
  • My goal: build my own game engine using Lazarus
Re: Please clarify the use of "virtual"
« Reply #6 on: September 03, 2020, 08:51:03 pm »
IMHO methods not marked "virtual" can also be overwritten.

Are you sure? Let's try the simple code I provided below. Lets see if you remove the 'virtual' on the base class, what will happen?

Confused.

Words become less confusing if you try them in code.
The base class is TAnimal line #28.
 
Here is how should to test the code:
  • Run the code and see will happen.
  • Remove the 'virtual' in the base class line #30. Run it and see what will happen.
  • Undo the step #2 and remove those 'override' on both line #39 and line #46. Run it and see what will happen.
  • Put 'virtual' on both the line #39 and line #46. Run it and see what will happen.
Hope this simple exercise can make you less confused.

For dynamic, as @jamie said it is useful in the old Delphi days. It is more memory-efficient but sacrificing (a bit) performance, because the computers in the olden days have very limited memory.

Sorry, I use Lazarus. So it is easier for me to write Lazarus examples instead of FPC.

Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Forms, Dialogs, StdCtrls;
  9.  
  10. type
  11.  
  12.   { TForm1 }
  13.  
  14.   TForm1 = class(TForm)
  15.     Button1: TButton;
  16.     procedure Button1Click(Sender: TObject);
  17.   end;
  18.  
  19. var
  20.   Form1: TForm1;
  21.  
  22. implementation
  23.  
  24. type
  25.  
  26.   { TAnimal }
  27.  
  28.   TAnimal = class
  29.   protected
  30.     function Sound: string; virtual;
  31.   public
  32.     procedure DoVocal;
  33.   end;
  34.  
  35.   { TCat }
  36.  
  37.   TCat = class(TAnimal)
  38.   protected
  39.     function Sound: string; override;
  40.   end;
  41.  
  42.   { TBigCat }
  43.  
  44.   TBigCat = class(TCat)
  45.   protected
  46.     function Sound: string; override;
  47.   end;
  48.  
  49. {$R *.lfm}
  50.  
  51. { TAnimal }
  52.  
  53. function TAnimal.Sound: string;
  54. begin
  55.   Result := '';
  56. end;
  57.  
  58. procedure TAnimal.DoVocal;
  59. begin
  60.   ShowMessage(Sound);
  61. end;
  62.  
  63. { TCat }
  64.  
  65. function TCat.Sound: string;
  66. begin
  67.   Result := 'meow';
  68. end;
  69.  
  70. { TBigCat }
  71.  
  72. function TBigCat.Sound: string;
  73. begin
  74.   Result := 'roar';
  75. end;
  76.  
  77. { TForm1 }
  78.  
  79. procedure TForm1.Button1Click(Sender: TObject);
  80. var
  81.   aTiger: TBigCat;
  82. begin
  83.   aTiger := TBigCat.Create;
  84.   aTiger.DoVocal;
  85.   aTiger.Free;
  86. end;
  87.  
  88. end.

You should now know if the TAnimal.Sound is not a virtual method or the TBigCat.Sound does not override it, the result aTiger.DoVocal is always empty. You may ask why.

Because the declaration of DoVocal is in TAnimal, it is belong to TAnimal. So DoVocal will call the Sound method that belong to TAnimal (if the Sound is not virtual or it is not overridden) even you call DoVocal from TBigCat. That happens because the compiler bind the method by hardcoding it at the compile time.

The virtual-overridden tells the compiler to use VMT (virtual method table) when calling the method. Using the data, it can know which method should be called. It is called Late Binding. Read more: https://en.wikipedia.org/wiki/Late_binding

The generation of the VMT data happens when the constructor being called. That's why we must always call the constructor before using it.

If you follow my explanation, you may wonder why not all the methods being set as virtual-override automatically. The generation of VMT need memory and using it is slower than direct non-virtual method. So don't do it if it is not needed.
« Last Edit: September 04, 2020, 01:29:04 pm by Handoko »

jamie

  • Hero Member
  • *****
  • Posts: 7653
Re: Please clarify the use of "virtual"
« Reply #7 on: September 03, 2020, 09:18:54 pm »
I wanted to clear up a little aspect about the NON virtual methods.

If you declare a non virtual method at the top of the derived classes that will be the one that gets called only when you are calling it from your code directly. While in there, you can call the INHERITED  which then calls the next one down and also the same holds true for the ancestors.

 What you can do with NON virtual methods that you can't do with virtual methods is cast directly to one of the ancestors and have that method called without it calling the last one that got implemented at the top of the inheritance . So even if you were to hide the ancestor you still can execute the ancestor directly there by skipping all the others or use the Inherited key word which simply calls the next one inline.

 Casting to a Virtual method in the ancestors will not get you any where, it will still call the first one at the top or at least it should ;)

The only true wisdom is knowing you know nothing

speter

  • Hero Member
  • *****
  • Posts: 522
Re: Please clarify the use of "virtual"
« Reply #8 on: September 04, 2020, 01:48:43 am »
One extra piece to the puzzle... there are also "abstract" methods.
I use them to a base class to "force" the descendant class(es) to implement a method...

For example:

Code: Pascal  [Select][+][-]
  1.   TPrimitive = class
  2.     NumPoints : integer; // numpoints in shape
  3.     points : TPointList;
  4.     kind : TShapeKind;
  5.     Colour : TColor;
  6.     completed : boolean;
  7.  
  8.     constructor create(c : tcolor);
  9.     procedure open(var f : textfile);
  10.     procedure save(var f : textfile);
  11.  
  12.     procedure draw; virtual; abstract;
  13.   end;
  14.  
  15.   TEllipse = class(TPrimitive)
  16.     constructor create(c : tcolor);
  17.     constructor create(x,y : integer; c : tcolor); overload;
  18.     constructor create (p1,p2 : tpoint; c : tcolor); overload;
  19.     procedure draw; override;
  20.   end;
  21.  

The implementation of TPrimitive does NOT include a draw() method.
The implementation of TEllipse MUST include a draw() method.
Abstract methods are very common in Java; I don't know how common they are in FPC/Laz.

If you want to look at all the code in the above application, there is a link to a zip file in this thread:
https://forum.lazarus.freepascal.org/index.php/topic,51107.msg374676.html

Steve
--
lazarus 2.0.8 / fpc 3.0.4 / svn 62944 / x86_64-win64-win32/win64
windows 10 (64 bit) ver 1909

I climbed mighty mountains, and saw that they were actually tiny foothills. :)

kupferstecher

  • Hero Member
  • *****
  • Posts: 617
Re: Please clarify the use of "virtual"
« Reply #9 on: September 04, 2020, 02:30:48 am »
In case its not clear, yet, here again in other words:
1. Non-virtual inheritance:
The correct method to be called is defined on compile time. Whatever type the variable has, the according methid will be called. You can also manipulate that by type casting.

2. Inheritance using virtual and override:
The compiler creates code to check the actual type on runtime* and calls the correct method of that type, even if you call it from a variable with the type of a base class, while it actually contains an instance of a derived class/child class.

Example of 2.: A TButton on a form. If you close the form. As owner of the button the form will go through it's list of all it's owned controls and frees them. In the list the button is not in a variable with type TButton, but of an anchestor level, I'm not sure now, could be TComponent, but would work with TObject as well. It would be difficult (or impossible) to actually list the button as TButton, because there are so many derived types. Thanks to inheritance the form doesn't need to consider the actual type. It just calls Free, containing the button in a variable of type TComponent. Then the TButton.Free method will be correctly called instead of TComponent.Free, which it would be in the non-virtual case.

So using virtual/override activates some compiler magic.

*This is an abstraction, technically this is not true, it actually works with something like a function pointer, but the result is the same.

eny

  • Hero Member
  • *****
  • Posts: 1665
All posts based on: Win11; stable Lazarus 4_4  (x64) 2026-02-12 (unless specified otherwise...)

egsuh

  • Hero Member
  • *****
  • Posts: 1774
Re: Please clarify the use of "virtual"
« Reply #11 on: September 04, 2020, 11:12:04 am »
You should understand OOP first.  Key features are inheritance and polymorphism. 

ArminLinder

  • Sr. Member
  • ****
  • Posts: 320
  • Keep it simple.
Re: Please clarify the use of "virtual"
« Reply #12 on: September 04, 2020, 12:24:04 pm »
Thanks to all (probably except egsuh) for your efforts. It will take me a while to understand it all, but I am sure I can sort things out, the answers I need to make my brain say "click" is somewhere in your explanations.

Many thanks!

P.S. I think you already suspected it, the whole OOP stuff makes my stomach revolt regularly. So much complexity, for almost nothing. Dykstra was right.
Lazarus 3.3.2 on Windows 7,10,11, Debian 10.8 "Buster", macOS Catalina, macOS BigSur, VMWare Workstation 15, Raspberry Pi

Handoko

  • Hero Member
  • *****
  • Posts: 5526
  • My goal: build my own game engine using Lazarus
Re: Please clarify the use of "virtual"
« Reply #13 on: September 04, 2020, 01:27:06 pm »
OOP concept is not a simple thing, no wonder you have problem understand it. With patient and practices, you will master it some day.

jamie

  • Hero Member
  • *****
  • Posts: 7653
Re: Please clarify the use of "virtual"
« Reply #14 on: September 04, 2020, 01:46:39 pm »
Its like having kids, they inherit your traits and when they grow up you are too tired to deal with things so you give it to your kids to worry about (Derived) and so when the kids get the task of figuring it out, they may first ask their parents for advice (inherited Call) , the parents will give them advice and from that the kids will complete the job or
the kids will try to do alone and then ask the parents for advice ( late Inherited call).Most kids do that ;)

 But what if the parents both kicked the bucket ? There are no inheritance to call on any more, so there is no call being made unless you want to talk the ski, it may answer who knows! ;)

 Basically if you don't ask the parents for assistance on the code then the kids go it by themselves and ignore all of their ancestors advice.

 There...

The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018