Reputation: 307
I've tried to map it out in my head, but honestly I have no idea what's really going on here.
What exactly is happening when I add and remove the virtual keyword from the below example?
#include <iostream>
#include <string>
class A {
public:
A() { me = "From A"; }
void caller() { func(); }
virtual void func() { std::cout << me << std::endl; } // THIS LINE!
private:
std::string me;
};
class B : public A {
public:
B() { me = "From B"; }
void func() { std::cout << me << std::endl; }
private:
std::string me;
};
int main() {
A a;
a.caller();
B b;
b.caller();
return 0;
}
With the virtual keyword, it prints "From A", then "From B".
Without the virtual keyword, it prints "From A", then "From A".
So far, this is the only time I've found a use for virtual functions without pointers being involved. I thought that if the virtual keyword was removed, the compiler would do the standard thing which is to overload the inherited function and end up printing "From A", and "From B" anyway.
I think this is deeper than just the VTable, and that it's more about the way it behaves in particular circumstances. Does B even have a VTable?
Upvotes: 0
Views: 112
Reputation: 73542
In your example, you won't see the difference:
With the virtual function, the compiler will generate a call via the VTable and at runtime, each objects will call the right function for their real class.
With the non virtual function, the compiler determines at compile time the right function to call, based on the objects defined class.
Now try the following, to see the virtual function in action:
A *pa = &b; // pointer to an A: valid as b is a B wich is also an A.
pa -> caller(); // guess what will be called if virtual or not.
No need for pointer to experimenting with virtual functions. You can observe the same effect with references as well:
A& ra = b; // create a reference to an A, but could as well be a parameter passed by reference.
ra.caller();
Virtual functions are useful for polymorphism. The idea is that you work with a general object of a class, but you don't know at compile time, if at runtime the object will really be of this class, or if it will not be a more specialiszed object (inheriting from the class).
Upvotes: 0
Reputation: 145429
The call
func()
is equivalent to
this->func()
so there is a pointer involved.
Still, there's no need to involve pointers to understand the behavior.
Even a direct call of e.g. b.func()
has to work as if it's a virtual call, when func
is virtual in the statically known type. The compiler can optimize it based on knowing the most derived type of b
. But that's a different kind of consideration (optimizations can do just about anything).
Upvotes: 1
Reputation: 2373
Apart from the issue of virtual dispatch, what may bring extra confusion, is that you have two me
s, one declared in A
and another declared in B
. These are two distinct objects.
An object of type B
has two data members of type std::string
; one on its own, and one incorporated into the subobject of type A
. The latter one, though, is not immediately available in the methods of type B
because its name is eclipsed by the new me
introduced in this class (though you may use a qualified name, A::me
to refer to it).
Therefore, even though the bodies of A::func
and B::func
seem identical, the identifier me
used in both of them refers to different members.
Upvotes: 0