Revision [24064]

This is an old revision of ProPgPolymorphism made by fxm on 2020-07-24 00:35:06.


Inheritance Polymorphism

The Inheritance Polymorphism is the ability of calling from the base-type the member procedures of derived-types without worrying about the real type of the processed objects.

Inheritance polymorphism is the concept of providing a single interface to entities that can have different types.
More precisely, a same interface is implemented by member procedures having the same identifier in each type belonging to the same inheritance hierarchy.

Thanks to the 'abstract'/'virtual' procedures, one can write a code using only the base-type that will automatically call the derived-type procedures.
It is then possible to call the procedure of an object without worrying about its intrinsic type.

By using the same procedure name for several different types, the polymorphism allows a much more generic programming (abstraction).
The coder does not have to know, when calling a base procedure, the precise type of object on which the procedure will apply. He just needs to know that this type will implement the procedure.

For example a procedure 'moving()' will perform the appropriate movement according to the real derived-type of the instance referenced at the time of the call. This will allow the program to say 'instance.moving()' without having to worry about the real derived-type of 'instance'.

Inheritance polymorphism operating
The ability to redefine a procedure in a derived-type inheriting from a base-type is called specialization.
It is then possible to call the procedure of an object without worrying about its intrinsic type: it is the inheritance polymorphism.

This makes it possible to abstract the details of the specialized types of an object family, by masking them by a common interface which is the base-type.

Designation of objects using pointers or references of base-type
Considering a collection of objects whose instantiate types are derived-types from a base-type, then all these objects can be manipulated in an uniform way by considering them as objects of the base-type.
Better, certain behaviors can be specialized according to the instantiate type of each object. In other words, the use of distinct objects of the same inheritance hierarchy is homogeneous even if the behavior of these objects remains specific.

Thus, a base-type pointer or reference, pointing to an instance of a derived-type, can be used to manipulate such an object.

Overriding the abstract/virtual procedures in the base-type by specialized procedures in derived-types
To can declare abstract/virtual procedures in a type, this type must 'Extends' (directly or indirectly) the built-in 'Object' type.

A derived-type can override an abstract/virtual procedure declared in its base-type, by declaring a procedure with the same identifier and signature, meaning same number and type of parameters, same calling convention, and if any, same return type (or a return of a derived-type for return by reference or by pointer):
- Normally a base-type reference/pointer can access only a procedure in the same type or in a type upper in hierarchy (static binding at compile-time), even if this reference/pointer refers to an object of instantiate type derived from the base-type.
- But when the base-type procedure is abstract/virtual, this tells the running program to resolve the override procedure the most derived relating to the real object type (dynamic binding at run-time).

Mechanism under the hood for inheritance polymorphism
The abstract/virtual member procedures are implemented using virtual procedure tables (vtbl). vtbl is, simply explained, a table of static procedures pointers.
The compiler fills a vtbl for each polymorphic type, i.e. a type defining at least an abstract/virtual procedure or a type derived from the former.
vtbl contains entries for all abstract/virtual procedures available in the type, including the abstract/virtual procedures defined in upper level of inheritance hierarchy (for abstract procedure not still implemented, a null pointer is set in the vtbl).

Each vtbl contains the correct addresses of procedures for each abstract/virtual procedure in corresponding type. Here correct means the address of the corresponding procedure of the most derived-type that defines/overrides that procedure.
When the type is instantiated, the instance will contain a pointer (vptr) to the virtual procedure table (vtbl) of the instantiated type.
When an object of a derived-type is referenced within a pointer/reference of base-type, then abstract/virtual procedure feature really performs. The call of an abstract/virtual procedure is somehow translated at run-time and the corresponding procedure from the virtual procedure table of the type of underlying object (not of the pointer/reference type) is chosen.
Thus, what procedure is called depends on what the real type of object the pointer/reference points to, which can't be known at compile-time, that is why the abstract/virtual procedure call is decided at run-time.

Therefore, the abstract/virtual procedure call (by means of a pointer or a reference) is not an ordinary call and has a little performance overhead, which may turn into a huge if we have numerous calls.
The abstract/virtual procedure call is converted by compiler to something else by using the proper vtbl addressed by the vptr value (located at offset 0 in the instance data):
In the below example (1), the three calls:

are about translated by the compiler into respectively:
Cptr(Function (Byref As animal) As animal Ptr, Cptr(Any Ptr Ptr Ptr, animal_list(I))[0][0])(*animal_list(I))
Cptr(Function (Byref As animal) As String, Cptr(Any Ptr Ptr Ptr, animal_list(I))[0][1])(*animal_list(I))
Cptr(Sub (Byref As animal), Cptr(Any Ptr Ptr Ptr, animal_list(I))[0][2])(*animal_list(I))

- The first indirection '[0]' allows to access to the value of the vptr from the address of the instance. This value correspond to the address of the vtbl.
- The second indirection '[0]' or '[1]' or '[2]' allows to access in the vtbl to the static address of the abstract procedures 'addr_override_fct()' or 'speak_override_fct()' or 'type_override_sub()' respectively (in the declaration order of the abstract or virtual procedures of the Type structure).

For the vptr value, the compiler generates some extra code in the constructor of the type, which it usually adds before the user code. Even if the user does not define a constructor, the compiler generates a default one, and the initialization of vptr is there. So each time an object of a polymorphic type is created, vptr is correctly initialized and points to the vtbl of that type.

The built-in 'Object' type also provides the RTTI (Run-Time Type Information) capacity for all types derived from it using the 'Extends' declaration. The RTTI capacity allows to determine the real type of an object at run-time, which can be different of its at compile-time.
The 'operator Is (rtti)' uses it to check if an object is compatible to a type derived from its compile-time type, because RTTI provides not only the real runtime type-name of the object but also all type-names of its base types, up to the 'Object' built-in type.
Nevertheless these type-names stored by RTTI (referenced by a specific pointer in the vtbl) are mangled names inaccessible directly from a FreeBASIC keyword.

How are chained the entities: object instance, vptr, vtabl (vtable), and RTTI info:
- Instance -> Vptr -> Vtbl -> RTTI info chaining:
- For any type derived (directly or indirectly) from the Object built-in type, a hidden pointer vptr is added at beginning (located at offset 0) of its data fields (own or inherited). This vptr points to the virtual table vtbl of the considered type.
- The vtbl contains the list of the addresses of all abstract/virtual procedures (from the offset 0). The vtbl also contains (located at offset -1) a pointer to the RunTime Type Information (RTTI) info block of the considered type.
- The RTTI info block contains (located at offset +1) a pointer to the mangled-typename of the considered type (ascii characters). The RTTI info block also contains (located at offset +2) a pointer to the RTTI info block of its Base. All RTTI info blocks for up-hierarchy are so chained.
- Instance -> Vptr -> Vtbl -> RTTI info diagram:
'                                      vtbl (vtable)
'                                  .-------------------.
'                              [-2]|   reserved (0)    |               RTTI info            Mangled Typename
'                                  |-------------------|       .-----------------------.       .--------.
'         Instance of UDT      [-1]| Ptr to RTTI info  |--->[0]|     reserved (0)      |       | Length |
'      .-------------------.       |-------------------|       |-----------------------|       | (ASCII)|
'   [0]| vptr: Ptr to vtbl |--->[0]|Ptr to virt proc #1|   [+1]|Ptr to Mangled Typename|--->[0]|    &   |
'      |-------------------|       |-------------------|       |-----------------------|       |Typename|
'      |UDT member field #a|   [+1]|Ptr to virt proc #2|   [+2]| Ptr to Base RTTI info |---.   | (ASCII)|
'      |-------------------|       |-------------------|       |_______________________|   |   |________|
'      |UDT member field #b|   [+2]|Ptr to virt proc #3|   ________________________________|
'      |-------------------|       :- - - - - - - - - -:  |
'      |UDT member field #c|       :                   :  |             Base RTTI info
'      :- - - - - - - - - -:       :                   :  |       .----------------------------.
'      :                   :       |___________________|  '--->[0]|        reserved (0)        |
'      :                   :                                      |----------------------------|
'      |___________________|                                  [+1]|Ptr to Mangled Base Typename|--->
'                                                                 |----------------------------|
'                                                             [+2]| Ptr to Base.Base RTTI info |---.
'                                                                 |____________________________|   |
'                                                                                                  |
'                                                                                                  V

Polymorphism is not directly compatible with:
- any operators 'New[]' or 'Delete[]' (the array versions for statement/expression/overload operators) because the use of a sub-type pointer (instead of the real type) fails for accessing the other elements (except the first),
- even the overload operator 'Delete' is not directly compatible because it can not be declared virtual (being static).
Instead of having to call such an operator 'Delete([])' statement on derived-type pointer, the safest way is to simply call (on base-type pointer) an overridden user virtual member procedure that will automatically launch the operator 'Delete([]') statement at derived-type level.

Inheritance polymorphism learning, through example (1): 'Animal type collection'
In the below proposed example, the polymorphic part is broken down to better bring out all the elements necessary for the mechanics of polymorphism.

The generic base-type chosen is any 'animal' (abstraction).
The specialized derived-types are a 'dog', a 'cat', and a 'bird' (each defining a non-static string member containing its type-name).
The abstract procedures declared in the generic base-type, and which must be defined in each specialized derived-type, are:
- 'addr_override_fct()': returns the instance address,
- 'speak_override_fct()': returns the way of speaking,
- 'type_override_sub()': prints the type-name (from a string member with initialyzer).

Inheritance polymorphism learning, through example (2): 'Graph type collection'
In the below proposed example, the polymorphic part is broken down to better bring out all the elements necessary for the mechanics of polymorphism.

The generic base-type chosen is any 'Graphic Form' defined by two graphic points and a color (abstraction).
The specialized derived-types are a 'Graphic Line', a 'Graphic Box', and a 'Graphic Circle' (all defined by two graphic points and a color):
- The 'Graphic Line' connects the point 1 and the point 2.
- The 'Graphic Box' has as opposite vertices the point 1 (at the top and on the left) and the point 2 (in bottom and on the right).
- The 'Graphic Circle' has as center the point 1 and go through point 2.

The abstract procedure declared in the generic base-type, and which must be defined in each specialized derived-type, is the graphic drawing of the specialized form in a graphic window.
The two graphic points and the color being generic data, they so induce three generic data fields in the generic base-type, included by composition.
A 'graphic point' type is also defined with encapsulation of the x/y coordinate values (declared private), in order to control their validity (depending on the defined graphic screen size) by means of properties (but public these ones).

- Generic base-type name: 'GraphicForm2P'
- Specialized derived-type names: 'GraphicLine2P', 'GraphicBox2P', 'GraphicCircle2P'
- Virtual procedure name: 'drawGraphicForm2P()'
- Additional type name (include by composition within the generic type): 'GraphicPoint'

See also
Back to Programmer's Guide
Valid XHTML :: Valid CSS: :: Powered by WikkaWiki phatcode