Reputation: 61
I found some excellent threads on this site for the subject, and the topic of polymorphism is cleared up for me, but I'm just confused how exactly a virtual function works versus a normal function.
(an example given in this thread Why do we need virtual functions in C++?):
class Animal
{
public:
void eat() { std::cout << "I'm eating generic food."<<endl; }
};
class Cat : public Animal
{
public:
void eat() { std::cout << "I'm eating a rat."<<endl; }
};
void func(Animal *xyz) { xyz->eat(); }
So We have a function and a derived function that was redefined.
Cat *cat = new Cat;
Animal *animal = new Animal;
animal->eat(); // Outputs: "I'm eating generic food."
cat->eat(); // Outputs: "I'm eating a rat."
func(animal); // Outputs: "I'm eating generic food."
func(cat); // Outputs: "I'm eating generic food."
So we can't access the function we want without it being a virtual function. But why, exactly?
If the following works:
Cat cat;
Animal animal;
animal.eat(); // Outputs: "I'm eating generic food."
cat.eat(); // Outputs: "I'm eating a rat."
Then presumably there are two different eat functions in memory already without needing a vtable.
So when we make eat a virtual function, each class now gets its own vTable with its own functions. So...We are just storing the functions in another place in memory. So what happens to a pointer between when it calls a regular function through an object and when it calls a virtual function through an object?
As in what's the difference between: Animal->eat(); //Calling a virtual function and Animal->eat(); //Calling a regular function
When we declare virtual function, TutorialsPoint says
This time, the compiler looks at the contents of the pointer instead of it's type
Yes, but how? Why couldn't it do that before? It's presumably just stored in memory the same as a regular function. Does it have something to do with the Vtable pointer at the beginning of an object?
I'm just looking for specifics to understand why. I don't mean to sound like I'm getting bogged down in something ultimately pointless. Just wondering for academic reasons.
Upvotes: 0
Views: 334
Reputation: 275600
I find it best to implement it to understand it.
struct ifoo;
struct ifoo_vtable{
void(*print)(ifoo const*);
};
struct ifoo{
ifoo_vtable const* vtable;
void print()const{ vtable->print(this); }
};
struct fooa:ifoo{
void print_impl()const{ std::cout<<"fooa\n"; }
fooa(){
static const ifoo_vtable mytable={
+[](ifoo const* self){
static_cast<fooa const*>(self)->print_impl();
}
};
vtable=&mytable;
}
};
struct foob:ifoo{
void print_impl()const{ std::cout<<"foob\n"; }
foob(){
static const ifoo_vtable mytable={
+[](ifoo const* self){
static_cast<foob const*>(self)->print_impl();
}
};
vtable=&mytable;
}
};
now there is zero use of virtual
above. But:
fooa a;
foob b;
ifoo* ptr = (rand()%2)?&a;&b;
ptr->print();
will randomly call either fooa or foob's print_impl method.
The vtable is a structure of pointers to functions. Invoking a virtual methid actuall runs a little stub that looks up the method in the vtable, then runs it.
Code written for you in implementatiin classes' constructors fills that vtable with function pointers pointing at the overrides.
Now there are details that are not done here -- calling conventions, destructors, devirtualization, multiple interface inheritance, dynamic casts, etc -- but the core is pretty similar to how every major C++ compiler implements virtual methods.
Under the standard this is not detailed; only behaviour is. But this kind of vtable comes from before C++ was a language, and this kind of vtable is what I believe the C++ language designers had in mind when they specified the behaviour of virtual functions in C++.
Note that this is far from the only way to do this. MFC message maps, objective C/small talk based messages, python, lua, and many other languages have other ways to do this with advantages and disadvantages over C++'s solution.
Upvotes: 0
Reputation: 182789
Consider this code:
void Function(Animal *foo)
{
foo->eat();
}
If eat
is a non-virtual member function, this just calls Animal::eat
. It makes no difference what foo
points to.
If eat
is a virtual member function, this is roughly equal to *(foo->eatPtr)();
. You can think of Animal
, and all its derived classes, as having a member variable that points to the eat
function. So if foo
actually points to a Bear
, then foo->eatPtr()
will access Bear::eatPtr
and call the eat
function of the Bear
class.
Which function to call is determined at compile time for non-virtual functions. So this will always call the same eat
function. For a virtual function, the pointer passed in is used to find the appropriate virtual function table for the particular class that foo
happens to be a member of.
This extra class member variable that points to the vtable for the class is why the size of either a class instance or its pointers (depending on the implementation) will typically go up by the size of one pointer when you add the first virtual
function to that class.
Upvotes: 2