How to decide on virtual or shadow method

General FreeBASIC programming questions.
sancho3
Posts: 358
Joined: Sep 30, 2017 3:22

How to decide on virtual or shadow method

Postby sancho3 » Feb 15, 2019 22:58

How do you decide which way to code a class method in regards to shadowing or virtual methods?
In the following code example which method is better, virtual or shadowed?
The methods shadow() and over_ride() give the same output and seem to work the same.

Code: Select all

Type parent Extends Object
   as integer x
   Declare Sub shadow()
   Declare Virtual Sub over_ride()
End Type
   Sub parent.shadow()
      '
      Print "parent sub (shadow)"
   End Sub
   Sub parent.over_ride()
      '
      Print "parent Sub (over_ride)"
   End Sub

Type child Extends parent
   As Integer y
   declare Sub shadow()
   Declare Sub over_ride()
End Type
   Sub child.shadow()
      Print "child Sub shadowing parent sub"
      base.shadow()
   End Sub
   Sub child.over_ride()
      Print "child Sub overriding parent Sub"
      base.over_ride()
   End Sub
   
Dim As child c
c.shadow()
c.over_ride()
Getkey()


In my real world code I have a stack as a parent object and added some member fields to a derived child.
For example, I added a count variable.
So I shadow the push() and pop() methods and update the count member, then call the base.push() and base.pop().
Is there any reason I should change my parent to virtual methods?
fxm
Posts: 10037
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to decide on virtual or shadow method

Postby fxm » Feb 16, 2019 6:08

fxm wrote:Dynamic polymorphism

  • The dynamic polymorphism (brought through the inheritance and the virtuality) allows to similarly process heterogeneous objects, without knowing their real types at compile time. Their real types will be only used automatically at run time (dynamic binding).
    Therefore, that behavior allows to process a collection of heterogeneous objects by a same processing code (for example in an iterating loop over all objects) regardless of their real types (simplified code and also reusable for any other derived objects), instead of individually processing each object.

  • The collection of heterogeneous objects can be achieved for example through base-type pointers grouped in an array (like 'Dim pa(0 To 3) As Animal Ptr => {@c1, @d1, @c2, @d2}' in the above example):
    • Normally only a base-type procedure (or upper in hierarchy) is accessible through a base-type pointer (static binding) even if this one refers to an object derived from base_type.
    • But when the procedure is virtual, this tells the running program to resolve the override procedure the most derived relating to the real object type by vtable lookup (dynamic binding at runtime), rather than procedure normally accessible from the raw base-type of the pointer (static binding at compile time).

Have you read the following documentations as well:
- VIRTUAL / ABSTRACT
- How and Why to make Abstraction by Subtype Polymorphism, with FB Syntax in UDTs (advanced)

The different examples in documentation show that we can simultaneously well process a collection of different derived-types objects, through base-type pointers only, by using dynamic polymorphism (with virtual/abstract procedures) instead to individually process each derived-type object, by using commonly a reference or a pointer of its own derived-type.

Normally only a base-type procedure (or upper in hierarchy) is accessible through a base-type pointer.
When the procedure is virtual, this tells the program to resolve the most-derived version of the procedure (with the same signature) relating to the real object type (and not the pointer type).


Extrapolated from your example:

Code: Select all

Type parent Extends Object
   as integer x
   Declare Sub shadow()
   Declare Virtual Sub over_ride()
End Type
   Sub parent.shadow()
      '
      Print "parent sub (shadow)"
   End Sub
   Sub parent.over_ride()
      '
      Print "parent Sub (over_ride)"
   End Sub

Type child1 Extends parent
   As Integer y
   declare Sub shadow()
   Declare Sub over_ride()
End Type
   Sub child1.shadow()
      Print "child1 Sub shadowing parent sub"
      base.shadow()
   End Sub
   Sub child1.over_ride()
      Print "child1 Sub overriding parent Sub"
      base.over_ride()
   End Sub
   
Type child2 Extends parent
   As Integer y
   declare Sub shadow()
   Declare Sub over_ride()
End Type
   Sub child2.shadow()
      Print "child2 Sub shadowing parent sub"
      base.shadow()
   End Sub
   Sub child2.over_ride()
      Print "child2 Sub overriding parent Sub"
      base.over_ride()
   End Sub


Dim As child1 c1
Dim As child2 c2
Dim As parent Ptr p(1 To 2) = {@c1, @c2}

Print "- Accessing method through derived-type references:"
Print
c1.shadow()
c2.shadow()
Print
c1.over_ride()
c2.over_ride()
Print
Print
Print "- Accessing method through base-type pointers:"
Print
For I As Integer = 1 To 2
   p(I)->shadow()
Next I
Print
For I As Integer = 1 To 2
   p(I)->over_ride()
Next I

Getkey()
- Accessing method through derived-type references:

child1 Sub shadowing parent sub
parent sub (shadow)
child2 Sub shadowing parent sub
parent sub (shadow)

child1 Sub overriding parent Sub
parent Sub (over_ride)
child2 Sub overriding parent Sub
parent Sub (over_ride)


- Accessing method through base-type pointers:

parent sub (shadow)
parent sub (shadow)

child1 Sub overriding parent Sub
parent Sub (over_ride)
child2 Sub overriding parent Sub
parent Sub (over_ride)


When dynamic binding at run time (using override procedures) is not requested (when no abstraction is necessary in the processing of derived objects), a simpler static binding at compile time (using hiding procedures) is preferable because it induces faster performance due to the additional indirection (via a vptr + a vtable) added when using virtual methods.

Note on the indirection when calling a virtual procedure
As 'over_ride()' is the first declared virtual procedure of the Inheritance hierarchy (so procedure address is at offset=0 within the vtable referenced by vptr which is at offset=0 within the object):
  • 'c1.over_ride()' runtime real code is equivalent to:
    'Cptr(Sub(Byref As parent), Cptr(Any Ptr Ptr Ptr, @c1)[0][0])(c1)'
  • 'c2.over_ride()' runtime real code is equivalent to:
    'Cptr(Sub(Byref As parent), Cptr(Any Ptr Ptr Ptr, @c2)[0][0])(c2)'
  • 'p(I)->over_ride()' runtime real code is equivalent to:
    'Cptr(Sub(Byref As parent), Cptr(Any Ptr Ptr Ptr, p(I))[0][0])(*p(I))'

    The general syntax of equivalent runtime code for accessing a member virtual procedure declared at rank 'N' (0-based) in the virtual procedure hierarchy, and called on an object 'obj' derived from a type 'Base', is the following:
    'Cptr(Sub(Byref As Base[, parameter-declarations]), Cptr(Any Ptr Ptr Ptr, @obj)[0][N])(obj[, parameter-values])'
    or:
    'Cptr(Function(Byref As Base[, parameter-declarations]) return-declaration, Cptr(Any Ptr Ptr Ptr, @obj)[0][N])(obj[, parameter-values])'

In summary:
  • If the member procedure is always called on references or pointers that are of the same type as the real type of the referenced object, it is advisable to use the shadowing method (without using a virtual procedure) because it induces the fastest access.
  • On the other hand, if one want to be able to also call a member procedure on references or pointers that are of a type inherited by the one of the referenced object (up-casting), you must use the overriding method (by using a virtual procedure), which is more flexible (also works for the previous case) but it induces slower access.

Return to “General”

Who is online

Users browsing this forum: No registered users and 11 guests