virtual
always has the same meaning when applied to a member function. When a member function is virtual
, it means that calls to that member function will be dispatched dynamically.
That is, the function to be called will be selected based on the dynamic type (the actual type), not the static type. Based on the actual type of the object, the final overrider of the virtual function will be called.
The only reason that destructors are "different" is that a derived class destructor has a different name than the base class destructor. The behavior of the derived class destructor is not affected by it being declared virtual
, though: a derived class destructor always calls base class destructors after it has run.
As an example of the behavior of virtual
functions:
struct B {
void f() { }
virtual void g() { }
};
struct D : B {
void f() { }
virtual void g() { }
};
int main() {
B* p = new D();
}
The dynamic type of the expression *p
is D
because the actual type of the object pointed to by p
is a D
. You used new D()
to create it, so it's dynamic type is D
.
The static type of the expression *p
is B
because that is the type named in the code. Without running the program or evaluating what got assigned to p
, the compiler doesn't know the most derived type of the object given by *p
; it just knows that whatever it is, it is a B
or something derived from B
.
If you call p->f()
, the call is dispatched statically because B::f
is not virtual
. The compiler looks at the static type of *p
and selects the function to be called based on that (B::f
).
If you call p->g()
, the call is dispatched dynamically because B::g
is virtual
. At runtime, the dynamic type of the object is checked (using a vtable in many common implementations), and the final overrider is called. In this case, the final overrider is D::g
because D
is the most derived class that overrides B::g
(if there was another class derived from D
, it could opt to override B::g
as well).