Virtual function and derived class

C++ compiler creates a lot of abstraction while constructing this vfptr, vtable and also while calling a virtual function of a class. To understand how this virtual function works, we have illustrated each section below with examples and with C language.

In the below example we have an example of a base class called CShape. This is an abstract base class. An abstract base class or ABC is a class which contains at least one pure virtual function.

class CShape
{
public :
  CShape();
  virtual void Draw(void) = 0;
  
}; 

Now we have derived two new shapes called CCircle and CRectangle from CShape.


class CCircle : public CShape
{

protected :
  int x, y, r;

public :
  CCircle(int x, int y, int r);
  virtual void Draw(void);
  
};

class CRectangle : public CShape
{

protected :
  int x, y, w, h;

public :
  CRectangle(int x, int y, int w, int h);

  virtual void Draw(void);
  
}; 

Calling Virtual function using base class pointer

In object oriented environment we always use an abstract base class pointer to point the derived object and call base function and the corresponding derived class function get called. This is why we use virtual function, with base pointer we call the virtual function and the derived class function gets called.

/* ------ CShape ----- */
CShape::Construct()
{
  printf("Constructing an abstract shape ");
}
/*-------------------*/
/* CCircle */
CCircle::CCircle(int x, int y, int r)
{
  this->= x;
  this->= y;
  this->= r;
  printf("Constructing CCircle x = %d, y = %d, r = %d ",
         this->x,
         this->y,
         this->r);
}
void CCircle::Draw(void)
{
  printf("Drawing CCircle x = %d, y = %d, r = %d ",
         this->x,
         this->y,
         this->r);
}
/*-------------------*/
/* ------ CRectangle ----*/
CRectangle::CRectangle(int x, int y, int w, int h)
{
  this->= x;
  this->= y;
  this->= w;
  this->= h;
  printf("Constructing Rectangle x = %d, y = %d, w = %d, h = %d ",
         this->x,
     this->y,
     this->w,
     this->h);
}
void CRectangle::Draw(void)
{
  printf("Drawing Rectangle x = %d, y = %d, w = %d, h = %d ",
         this->x,
     this->y,
     this->w,
     this->h);
}
/*-------------------*/
  
int main (int argc, char * argv[])
{ 
  CShape *shapes[2];

  shapes[0] = new CCircle(x1, y1, r);
  shapes[1] = new CRectangle(x2, y2, w, h);

  /* Now we call Draw() for both the shapes */
  for(= 0; i < 2; i++)
  {
     shapes[i]->Draw();
  }
}

Result:
Constructing an abstract shape
Constructing CCircle x = 10, y = 20, r = 100
Constructing an abstract shape
Constructing Rectangle x = 100, y = 100, w = 50, h = 40
Drawing CCircle x = 10, y = 20, r = 100
Drawing Rectangle x = 100, y = 100, w = 50, h = 40

The result is that, with base class pointer, without knowing the type of shapes, we called the Draw()[inside for loop] and with a magic it called the corresponding Draw() for each derived classes, like for Circle it called Draw() of CCircle and for rectangle it called Draw() of CRectangle, although for each time pointer is of CShape(base).

VPTR VTABLE Virtual function call - understanding using C

Now the point is, how it knows which function to call? Also how base pointer is related to the derived object's function address?

To elaborate this I want to use C language and some code using structure. Suppose we have a structure CShape. How I can relate the Draw() function to this structure?

The answer is simple, taking a function pointer inside the structure.

struct CShape
{
  void (*Draw)(void);
};

The problem with this approach is, each time I will add a new virtual function the size of the structure is going to increase by sizeof pointer. Like below if I add three virtual functions I am going to increase the size of structure by sizeof pointer x 3 thus each object of this type will consume this much of memory.

struct CShape
{
  void (*Draw)(void);
  void (*Draw1)(void);
  void (*Draw2)(void);
  ...
};

The solution is we can take one and only one pointer inside the structure to point to an array/table of function pointers. If we add more and more virtual functions, the size of the structure or object is not going to increase as it only holds the pointer to the table. But the size of the function pointer table will grow. This is acceptable as we increase the number of virtual function the table which records the location of the functions will surely going to increase. Now we have our picture ready as follows.

struct CShape
{
  struct CShape_vtable *vfptr;
};

This vfptr is called pointer to vtable. This is the first hidden member of any C++ class which has one or more virtual function.

struct CShape_vtable
{
  void (*Draw)(void);
  void (*Draw1)(void);
  void (*Draw2)(void);
  ...
};

vfptr points to a list/table or structure of function pointers also called vtable. This list/table is a built-in feature of C++ compiler. It holds the address of virtual functions for a particular class. Each class has its own vtable and vtable assignment is done by compiler after constructor is called and vtable gets de-assigned before destructor is called.

Now we will have our CShape, CCircle, CRectangle C++ code rewritten in C. This code is very elaborate and there has been given all the steps which compiler does for object construction. This will give a clear understanding.

/* ------ CShape ----- */
struct CShape_vtable;
struct CShape
{
  struct CShape_vtable *vfptr;
};
struct CShape_vtable
{
  void (*Draw)(void);
};
void CShape_Construct(struct CShape *_this)
{
  printf("Constructing an abstract shape ");
}
/*-------------------*/
/* CCircle */
struct CCircle_vtable;
struct CCircle
{
  struct CCircle_vtable *vfptr;
  int x, y, r;
};
struct CCircle_vtable
{
  void (*Draw)(void);
};
void CCircle_Construct(struct CCircle *_this, int x, int y, int r)
{
  _this->= x;
  _this->= y;
  _this->= r;
  printf("Constructing CCircle x = %d, y = %d, r = %d ",
         _this->x,
     _this->y,
     _this->r);
}
void CCircle_Draw(struct CCircle *_this)
{
  printf("Drawing CCircle x = %d, y = %d, r = %d ",
         _this->x,
     _this->y,
     _this->r);
}
/*-------------------*/
/* ------ CRectangle ----*/
struct CRectangle_vtable;
struct CRectangle
{
  struct CRectangle_vtable *vfptr;
  int x, y, w, h;
};
struct CRectangle_vtable
{
  void (*Draw)(void);
};
void CRectangle_Construct(struct CRectangle *_this,
                          int x, int y, int w, int h)
{
  _this->= x;
  _this->= y;
  _this->= w;
  _this->= h;
  printf("Constructing Rectangle x = %d, y = %d, w = %d, h = %d ",
         _this->x,
     _this->y,
     _this->w,
     _this->h);
}
void CRectangle_Draw(struct CRectangle *_this)
{
  printf("Drawing Rectangle x = %d, y = %d, w = %d, h = %d ",
         _this->x,
     _this->y,
     _this->w,
     _this->h);
}
/*-------------------*/

int main (int argc, char * argv[])
{ 
  /* Main code, C++ code has been commented.*/
  struct CShape *shapes[2];

  /* v-table construction, Built-in compiler hidden code*/
  struct CShape_vtable csvtable;
  struct CShape_vtable ccvtable;
  struct CRectangle_vtable crvtable;

  csvtable.Draw = 0; /* Pure virtual function */
  ccvtable.Draw = &CCircle_Draw;
  crvtable.Draw = &CRectangle_Draw;

  /* C++ Code shapes[0] = new CCircle(x1, y1, r); */
  /* Memory allocation */
  shapes[0] = (struct CShape *)malloc(sizeof(struct CCircle));

  /* CShape Base Constructor call */
  CShape_Construct(shapes[0]);

  /* CShape v-table assignment */
  shapes[0]->vfptr = &csvtable; 

  /* CCircle Derived Constructor call */
  CCircle_Construct(shapes[0], x1, y1, r); 

  /* CCircle v-table assignment */
  shapes[0]->vfptr = &ccvtable;

  /* C++ Code shapes[1] = new CRectangle(x2, y2, w, h);*/
  /* Memory allocation */
  shapes[1] = (struct CShape *)malloc(sizeof(struct CRectangle)); 

  /* CShape Base Constructor call */
  CShape_Construct(shapes[1]);

  /* CShape v-table assignment */
  shapes[1]->vfptr = &csvtable;

  /* CRectangle Derived Constructor call */
  CRectangle_Construct(shapes[1], x2, y2, w, h);

  /* CRectangle v-table assignment */
  shapes[1]->vfptr = &crvtable;

  /* Now we call Draw() for both the shapes */
  for(= 0; i < 2; i++)
  {
     shapes[i]->vfptr->Draw(shapes[i]);
  }
}

See here the call of the virtual function Draw(). Compiler calls the function pointer from the vtable through vfptr which is inside the object. Compiler calls a virtual function in three steps and they are -

  1. Get the vfptr,
  2. Goto the vtable and take the proper function pointer from the offset,
  3. Call the function using the pointer.
Note: These are compiled and tested in VC++ 6.0.

You may also like

Thanks for reading this answer. We hope you liked the content. These are some relevant contents people also visited. Hope you'll also wish to read these. Understand C++ vptr vtable virtual destructor Early binding Late binding virtual base class

About our authors: Team EQA

You have viewed 1 page out of 62. Your C++ learning is 0.00% complete. Login to check your learning progress.

#