Firstly, virtual function call does not require a pointer or a reference. As far as the language is concerned, any call to virtual function is a virtual call, unless you explicitly suppress the virtual dispatch mechanism by using a qualified function name. For example, these
d.D::call(); // calls `D::call()` directly
d.B::call(); // calls `B::call()` directly
are calls which were explicitly forced to be non-virtual. However, this
d.call(); // calls `D::call()` virtually
is a virtual call. In this case it is immediately obvious to the compiler that the target function is D::call()
, so the compiler normally optimizes this virtual call into a regular direct call. Yet, conceptually, d.call()
is still a virtual call.
Secondly, the call to call()
made inside B::runB()
is made through a pointer. The pointer is present there implicitly. Writing call()
inside B::runB()
is just a shorthand for (*this).call()
. this
is a pointer. So that call is made through a pointer.
Thirdly, the key property of virtual call is that the target function is chosen in accordance with the dynamic type of the object used in the call. In your case, even when you are inside B::runB()
the dynamic type of the object *this
is D
. Which is why it calls D::call()
, as it should.
Fourthly, what you really need a pointer or a reference for is to observe the actual polymorphism. Polymorphism proper occurs when static type of the object expression used in the call is different from its dynamic type. For that you do indeed need a pointer or a reference. And that is exactly what you observe in that (*this).call()
call inside B::runB()
. Even though the static type of *this
is B
, its dynamic type is D
and the call is dispatched to D::call()
.