Friday, Jan 19th

Last update12:59:40 PM GMT

VTABLE in Virtual Functions

Write e-mail

In the last article (Click here to read), we learnt how to use Virtual Functions. This was just one half of the story. Knowing how they work completes the other half.

argaiv1077

Its a known fact that when we instantiate a class then the size of the object is equivalent to the sum of the sizes of its data members. Member functions are created and placed in memory once and all the objects refer to the same member functions. But there is always a separate set of data members created for each object. So does that mean an object belonging to a class having just a member function and no data will have zero size. Of course not!! Since each object must have a distinct address the compiler in such case forces the object to be of the smallest possible non-zero size.

What if we have a class having one virtual function and one int data member? Will the corresponding object have size equivalent to an int ? Surprisngly No!! If you test this in a program you will see that the size of the object comes out to be sum of sizeof (int) + sizeof (void pointer). Void pointer?? Where did it come from?

This void pointer is what we call VPTR.

  • Whenever we have a class having at least one virtual function, then at compile time a VTABLE is constructed for the class. The VTABLE contains an array of function pointers pointing to the addresses of all the virtual functions of the class. Whenever this class is instantiated, a void pointer (VPTR) is included in that object which is initialised to point to the class' VTABLE.
  • The same thing is done for each of the derived classes derived from the above class (containing virtual function(s)). Only difference being that if the derived class does not define any virtual function of the base class, then the function pointer corresponding to that function in the derived class' VTABLE will point to the address of the base class version of that virtual function.

Now let us start clearing the things with an example.

/*base class having two virtual functions and a non-virtual function*/
class item 
{
public:
 virtual void cost(){printf("In Item::cost()\n");}
 virtual void category(){printf("In Item::category()\n");}
 void display(); // non-virtual function
};
void item::display(){printf("In Item::display()\n");}
/*derived class having both the virtual functions of the base class and extending the non-virtual function of the base class*/
class hammer:public item
{
public:
 void cost(){printf("In Hammer::cost()\n");}
 void category(){printf("In Hammer::category()\n");}
 void display(); // non-virtual function
};
void hammer::display(){printf("In Hammer::display()\n");}

/*derived class having the virtual function cost() of the base class*/
class screw:public item
{
public:
 void cost(){printf("In Screw::cost()\n");}
};
/*derived class having the virtual function category() of the base class*/
class bolt:public item
{
public:
 void category(){printf("In Bolt::category()\n");}
};
void main()
{
hammer h1;
screw s1;
bolt b1;
 
item *i=&h1;
i->cost(); //This will call hammer::cost() as cost() is virtual in base class and derived class hammer defines it
i->category();//This will call hammer::category() as category() is virtual in base class and derived class hammer defines it
i->display();//This will call item::display() as display is non-virtual in base class, hence base class version is called
 
i=&s1;
i->cost(); //This will call screw::cost() as cost() is virtual in base class and derived class screw defines it
i->category();//This will call item::category() as category is virtual in base class, but derived class screw does not define it
i->display(); //This will call item::display()
 
i=&b1;
i->cost();//This will call item::cost() as cost is virtual in base class, but derived class bolt does not define it
i->category();//This will call bolt::category() as category() is virtual in base class and derived class bolt defines it
i->display(); //This will call item::display()
}

It is easy to judge the output of the above program:

In Hammer::cost()
In Hammer::category()
In Item::display()
In Screw::cost()
In Item::category()
In Item::display()
In Item::cost()
In Bolt::category()
In Item::display() 

So if we follow the rules of VTABLE explained above, this is how the corresponding VTABLEs of each class will look like:

VPTR

 -->

VTABLE::item

&item::cost()

&item::category()

 

VPTR(h1)

         -->

VTABLE::hammer

&hammer::cost()

&hammer::category()

 

VPTR(s1)

         -->

VTABLE::screw

&screw::cost()

&item::category()

 

VPTR(b1)

         -->

VTABLE::bolt

&item::cost()

&bolt::category()

 

Based on the above VTABLES all the function calls are mapped to the correct address at runtime. This is called Late Binding (or Dynamic Binding).So in the above code, what i = &h1; does is that it looks at the VPTR of h1. VPTR(h1) will be pointing to the VTABLE of class hammer. Now when we make a call i->cost(); then the method pointed to by the corresponding function pointer of the VTABLE::hammer gets invoked. You can similarly try to see the invokation of other statements yourself.

Share this post



Add comment

Please refrain from using slang or abusive language in the comments.
To avoid waiting for your comment to be published after being reviewed, please log in as a registered user.


Security code
Refresh

Web Hosting