Date: Sat, 30 Jul 2022 23:25:29 +0200
On Sat, Jul 30, 2022 at 07:56:03PM +0100, Frederick Virchanza Gotham via Std-Proposals wrote:
> The term 'polymorphism' when used in the context of computer programming
> is typically applied to so-called 'virtual' methods.
>
> Virtual methods give us more versatility when dealing with classes, and
> in particular with the classifications of classes. At runtime, a
> pointer to 'Base' can be used to interact with objects of many types
> of derived classes -- however the relationships between the base and the
> derived classes are all set in stone at compile time (that is to say: All of
> the v-tables are compile-time constants).
>
> I wonder if we can take classes to the next level of versatility by no
> longer requiring the v-table to be a compile-time constant? What if the
> class could change at runtime as the program progresses from one state
> to another?
In OO litterature the class that describes a class is referred to as the
metaclass of the object. There is a proposal of that for C++ (p707) but
it does explicitly not allow changes to the metaclass after it's
construction.
> I'm going to use a really simple example. Let's say we have a base class
> called 'Mammal', with three derived classes, 'Cow', 'Sheep', 'Canine'.
> And furthermore 'Canine' has two derived classes, 'Dog' and 'Wolf'.
> Like this:
>
> Mammal
> |
> |
> |________ Cow
> |
> |
> |________ Sheep
> |
> |
> |________ Canine
> |
> |________ Dog
> |
> |________ Wolf
>
>
> The 'Mammal' class has a pure virtual method called 'Speak', and the
> 'Wolf' class overrides this method to print to the screen "Howl".
>
> Now let's say during the execution of the program, we want to change how
> Wolves work. Let's say we want them to say "Ow Ow Owwww" instead of "Howl".
> So we want to edit the v-table for 'Wolf' so that the pointer for the
> 'Speak' method points to some other method.
>
> I propose that the syntax for making this change to the v-table for 'Wolf'
> would be something like as follows on Line #13:
>
> 01: #include <iostream> // cout, endl
> 02: #include "animals.hpp" // Mammal, Cow, Sheep, Canine, etc.
> 03:
> 04: void New_Way_Of_Speaking(Wolf *const this)
> 05: {
> 06: std::cout << "Ow Ow Owwww" << std::endl;
> 07: }
> 08:
> 09: int main(void)
> 10: {
> 11: Wolf my_wolf;
> 12:
> 13: virtual Wolf::Speak = New_Way_Of_Speaking;
> 14: }
>
> The replacement method must be either:
> (A) Another method belonging to the "Wolf" class with the same signature
> (or a method with the same signature belonging to a base class
> of 'Wolf')
> (B) A free-standing function (or lambda) with the same signature except
> there is one more parameter inserted at the beginning, "Wolf
> *const this".
>
> In a multi-threaded program, the pointers in the V-table will have to be
> atomic.
So, you are introducing global variables in every class - that is not very
nice for multithreaded programs as every virtual method call require a
memory read since the method pointer might have changed.
> I have already written a sample program to show this in action. You can
> compile the following program for Linux with the GNU compiler (g++) as
> follows:
>
> g++ -o program source.cpp
>
> The program asks the user to select 'Decimal' or 'Hexadecimal' and then
> starts printing numbers to the screen. However it then starts alternating
> between decimal and hexadecimal as the V-Table is altered at runtime.
> This program works fine with the optimisation level set to (-O1) however
> it malfunctions if you set it to (-02) or (-03), presumably because there
> is caching of the v-table (I can't think of any other reason why).
The reason is that the compiler is allowed to replace virtual function calls
with direct calls. This extension basically kills that optimization so you
make everyone pay here.
Note that I don't claim this isn't a viable technique, I just think it is
better implemeted using a pImpl and that does also allow you to specify if
the object should be mutated on an per-instance level or an per-class level.
/MF
> The term 'polymorphism' when used in the context of computer programming
> is typically applied to so-called 'virtual' methods.
>
> Virtual methods give us more versatility when dealing with classes, and
> in particular with the classifications of classes. At runtime, a
> pointer to 'Base' can be used to interact with objects of many types
> of derived classes -- however the relationships between the base and the
> derived classes are all set in stone at compile time (that is to say: All of
> the v-tables are compile-time constants).
>
> I wonder if we can take classes to the next level of versatility by no
> longer requiring the v-table to be a compile-time constant? What if the
> class could change at runtime as the program progresses from one state
> to another?
In OO litterature the class that describes a class is referred to as the
metaclass of the object. There is a proposal of that for C++ (p707) but
it does explicitly not allow changes to the metaclass after it's
construction.
> I'm going to use a really simple example. Let's say we have a base class
> called 'Mammal', with three derived classes, 'Cow', 'Sheep', 'Canine'.
> And furthermore 'Canine' has two derived classes, 'Dog' and 'Wolf'.
> Like this:
>
> Mammal
> |
> |
> |________ Cow
> |
> |
> |________ Sheep
> |
> |
> |________ Canine
> |
> |________ Dog
> |
> |________ Wolf
>
>
> The 'Mammal' class has a pure virtual method called 'Speak', and the
> 'Wolf' class overrides this method to print to the screen "Howl".
>
> Now let's say during the execution of the program, we want to change how
> Wolves work. Let's say we want them to say "Ow Ow Owwww" instead of "Howl".
> So we want to edit the v-table for 'Wolf' so that the pointer for the
> 'Speak' method points to some other method.
>
> I propose that the syntax for making this change to the v-table for 'Wolf'
> would be something like as follows on Line #13:
>
> 01: #include <iostream> // cout, endl
> 02: #include "animals.hpp" // Mammal, Cow, Sheep, Canine, etc.
> 03:
> 04: void New_Way_Of_Speaking(Wolf *const this)
> 05: {
> 06: std::cout << "Ow Ow Owwww" << std::endl;
> 07: }
> 08:
> 09: int main(void)
> 10: {
> 11: Wolf my_wolf;
> 12:
> 13: virtual Wolf::Speak = New_Way_Of_Speaking;
> 14: }
>
> The replacement method must be either:
> (A) Another method belonging to the "Wolf" class with the same signature
> (or a method with the same signature belonging to a base class
> of 'Wolf')
> (B) A free-standing function (or lambda) with the same signature except
> there is one more parameter inserted at the beginning, "Wolf
> *const this".
>
> In a multi-threaded program, the pointers in the V-table will have to be
> atomic.
So, you are introducing global variables in every class - that is not very
nice for multithreaded programs as every virtual method call require a
memory read since the method pointer might have changed.
> I have already written a sample program to show this in action. You can
> compile the following program for Linux with the GNU compiler (g++) as
> follows:
>
> g++ -o program source.cpp
>
> The program asks the user to select 'Decimal' or 'Hexadecimal' and then
> starts printing numbers to the screen. However it then starts alternating
> between decimal and hexadecimal as the V-Table is altered at runtime.
> This program works fine with the optimisation level set to (-O1) however
> it malfunctions if you set it to (-02) or (-03), presumably because there
> is caching of the v-table (I can't think of any other reason why).
The reason is that the compiler is allowed to replace virtual function calls
with direct calls. This extension basically kills that optimization so you
make everyone pay here.
Note that I don't claim this isn't a viable technique, I just think it is
better implemeted using a pImpl and that does also allow you to specify if
the object should be mutated on an per-instance level or an per-class level.
/MF
Received on 2022-07-30 21:25:33