The whole point of virtual functions is that it is a runtime solution that is called based on the dynamic type of the object in question. This is very different from a non-virtual function call in which the compiler itself makes a decision based on the declared type of the object, regardless of what type the real runtime object belongs to.
To do this, each class has a virtual function table (vtable), which in all its instances has an implicit pointer at run time. Now when you create the Base instance, the vtable pointer of the instance will point to the vtable Base table. Similarly, an instance of Derived will have a pointer to Derived vtable.
In these two vtables in your example, there is only one entry for WhoAmI() , the pointer in Base vtable points to Base::WhoAmI() , and the pointer in Derived vtable points to Derived::WhoAmI() .
Therefore, when you call WhoAmI() , the runtime will look for a vtable from the object, and then a pointer to the function to be executed.
This suggests that, as you can see from the behavior you saw, a pointer to a member function is used: nothing more or less than the offset in the vtable! In your case, this offset is likely to be just zero, since WhoAmI() is the first and only entry in the vtable. When you call a function after the member function pointer, you give it the object from which vtable is viewed. And then the offset in the vtable (pointer to a member function) is used to load the pointer to the actual code that runs, as with any other virtual function call.
The only difference is that in a normal virtual function call, the compiler will know the exact offset at which to look for the function pointer by the name of the called function, when you use the pointer to the member function, this offset is provided at the time the pointer executes the member function.
source share