typedef int (*VirtualFunctionPointer)();
struct VTable
{
// d and i fields are used when multiple inheritance and virtual
// base classes are involved. We will be ignoring them for this
// discussion.
int d;
int i;
// A function pointer to the virtual function to be called is
// stored here.
VirtualFunctionPointer pFunc;
};
struct Shape
{
int m_x;
int m_y;
// The C++ compiler inserts an extra pointer to a vtable which
// will keep a function pointer to the virtual function that
// should be called.
VTable *pVTable;
};
struct Circle
{
// Fields inherited from Shape
int m_x;
int m_y;
VTable *pVTable;
// Fields added by Circle
int m_radius;
};
// The Shape vtable array contains entries for Draw and MoveTo
// virtual functions. Notice that there is no entry for Erase,
// as it is not virtual. Also, the first two fields for every
// vtable entry are zero, these fields might have non zero
// values with multiple inheritance, virtual base classes
// A third entry has also been defined for the virtual destructor
VTable VtableArrayForShape[] =
{
// Vtable entry virtual function Draw.
// Since Draw is pure virtual, this entry
// should never be invoked, so call error handler
{ 0, 0, pure_virtual_called_error_handler },
// This vtable entry invokes the base class's
// MoveTo method
{ 0, 0, Shape_MoveTo },
// Entry for the virtual destructor
{ 0, 0, Shape_Destructor }
};
// Vtable array for Circle
VTable VtableArrayForCircle[] =
{
// Vtable entry virtual function Draw.
// Circle_Draw method will be invoked when Shape's
// Draw method is invoked
{ 0, 0, Circle_Draw },
// This vtable entry invokes the base class's
// MoveTo method
{ 0, 0, Shape_MoveTo },
// Entry for the virtual destructor
{ 0, 0, Circle_Destructor }
};
Shape *Shape_Constructor(Shape *this, int x, int y)
{
//Check if memory has been allocated for struct Shape.
if (this == NULL)
{
//Allocate memory of size Shape.
this = malloc(sizeof(Shape));
}
//Once the memory has been allocated for Shape, initialise members of Shape.
if (this)
{
// Initialize the VTable pointer to point to shape
this->pVTable = VTableArrayForShape;
this->m_x = x;
this->m_y = y;
}
return this;
}
void Shape_Destructor(Shape *this, bool dynamic)
{
// Restore the VTable to that for Shape. This is
// required so that the destructor does not invoke
// a virtual function defined by a inheriting class.
// (The base class destructor is invoked after inheriting
// class actions have been completed. Thus it is not
// safe to invoke the ineriting class methods from the
// base class destructor)
this->pVTable = VTableArrayForShape;
. . .
//If the memory was dynamically allocated for Shape, explicitly free it.
if (dynamic)
{
free(this);
}
}
Circle *Circle_Constructor(Circle *this, int x, int y, int radius)
{
//Check if memory has been allocated for struct Circle.
if (this == NULL)
{
//Allocate memory of size Circle.
this = malloc(sizeof(Circle));
}
//Once the memory has been allocated for Circle, initialise members of Circle.
if (this)
{
// Invoking the base class constructor
Shape_constructor((Shape *)this, x, y);
this->pVtable = VtableArrayForCircle;
this->m_radius = radius;
}
return this;
}
void Circle_Destructor(Circle *this, bool dynamic)
{
// Restore the VTable to that for Circle
this->pVTable = VTableArrayForCircle;
. . .
// Invoke the base class destructor after ineriting class
// destructor actions have been completed. Also note that
// that the dynamic flag is set to false so that the shape
// destructor does not free any memory)
Shape_Destructor((Shape *) this, false);
//If the memory was dynamically allocated for Circle, explicitly free it.
if (dynamic)
{
free(this);
}
}
void Circle_Draw(Circle *this)
{
glib_draw_circle(this->m_x, this->m_y, this->m_radius);
}
main()
{
//Dynamically allocate memory by passing NULL in this arguement.
//Also initialse members of struct pointed to by pShape.
Shape *pShape = Circle_Constructor(NULL, 50, 100, 25);
//Define a local variable aCircle of type struct Circle.
Circle aCircle;
//Initialise members of struct variable aCircle.
Circle_Constructor(&aCircle, 5, 5, 2);
// Virtual function Draw is called for the shape pointer. The compiler
// has allocated 0 offset array entry to the Draw virtual function.
// This code corresponds to "pShape->Draw();"
(pShape->pVTable[0].pFunc)(pShape);
// Virtual function MoveTo is called for the shape pointer. The compiler
// has allocared 1 offset array entry to the MoveTo virtual function.
// This code corresponds to "pShape->MoveTo(100, 100);"
(pShape->pVTable[1].pFunc)(pShape, 100, 100);
// The following code represents the Erase method. This method is
// not virtual and it is only defined in the base class. Thus
// the Shape_Erase C function is called
Shape_Erase(pShape);
// Delete memory pointed to by pShape (explicit delete in original code).
// Since the destructor is declared virtual, the compiler has allocated
// 2 offset entry to the virtual destructor
// This code corresponds to "delete pShape;"
(pShape->pVTable[2].pFunc)(pShape, true);
// The following code corresponds to aCircle.Draw().
// Here the compiler can invoke the method directly instead of
// going through the vtable, since the type of aCircle is fully
// known. (This is very much compiler dependent. Dumb compilers will
// still invoke the method through the vtable)
Circle_Draw(&aCircle);
//Since memory was allocated from the stack for local struct
//variable aCircle, it will be deallocated when aCircle goes out of scope.
// The destructor will also be invoked. Notice that dynamic flag is set to
// false so that the destructor does not try to free memory. Again, the
// compiler does not need to go through the vtable to invoke the destructor.
Circle_Destructor(&a, false);
}
|