Wednesday, March 29, 2006

Vtable

Stolen from monash.edu site

CSE2305 - Object-Oriented Software Engineering
Week 5

Topic 10: C++ Polymorphism


Synopsis


Pointers, references¤ and inheritance

  • Recall that public inheritance¤ implies an "is a" relationship from child to parent
    class Vehicle
    {
    public:
    Vehicle(char* regnum)
    : myRegNum(strdup(regnum))
    {}

    ~Vehicle(void)
    { delete[] myRegNum; }

    void Describe(void)
    {
    cout << "Unknown vehicle, registration "
    << myRegNum << endl;
    }

    protected:
    char* myRegNum;
    };

    class Car : public Vehicle
    {
    public:
    Car(char* make, char* regnum)
    : Vehicle(regnum), myMake( strdup(make) )
    {}

    ~Car(void)
    { delete[] myMake; }

    void Describe(void)
    {
    cout << "Car (" << myMake
    << "), registration "
    << myRegNum << endl;
    }

    protected:
    char* myMake;
    };
  • One consequence of that relationship is that a base-class¤ pointer can point at a derived object without needing a cast
    Vehicle* vp1 = new Vehicle ("SGI 987");
    Vehicle* vp2 = new Car ("Jaguar","XJS 012");

    vp1->Describe(); // PRINTS "Unknown vehicle....."
    vp2->Describe(); // PRINTS "Unknown vehicle....."
  • Likewise, a base-class¤ reference¤ can refer to a derived object without a cast
    Vehicle v1 ("SGI 987");
    Car c1 ("Jaguar","XJS 012");

    Vehicle& vr1 = v1;
    Vehicle& vr2 = c1;

    vr1.Describe(); // PRINTS "Unknown vehicle....."
    vr2.Describe(); // PRINTS "Unknown vehicle....."
  • The problem is that when the compiler is working out which Describe() member¤ function to call, it selects according to the type of the pointer (Vehicle* or Vehicle& in both cases), rather than the type of the object being pointed at or referred to (Vehicle or Car)
  • So all the above calls to a Describe() member¤ are dispatched to Vehicle::Describe(), even when the pointer of reference¤ actually points to a Car object!
  • What we need here is polymorphism¤


Polymorphism¤ in C++

  • C++ provides a simple mechanism for class¤-based polymorphism¤
  • Member¤ functions can be marked as polymorphic using the virtual keyword
  • Calls to such functions then get dispatched according to the actual type of the object which owns them, rather than the apparent type of the pointer (or reference¤) through which they are invoked


Virtual functions¤

  • A member¤ function which is declared with the virtual keyword is called a virtual function¤
  • Once a function is declared virtual, it may be redefined in derived classes¤¤ (and is virtual in those classes¤ too)
  • When the compiler sees a call to a virtual function¤ via a pointer or reference¤, it calls the correct version for the object in question (rather than the version indicated by the type of the pointer or reference¤)


An example

class Vehicle
{
public:
Vehicle(char* regnum)
: myRegNum(strdup(regnum))
{}

~Vehicle(void)
{ delete[] myRegNum; }

virtual void Describe(void)
{
cout << "Unknown vehicle, registration "
<< myRegNum << endl;
}

protected:
char* myRegNum;
};

class Car : public Vehicle
{
public:
Car(char* make, char* regnum)
: Vehicle(regnum), myMake( strdup(make) )
{}

~Car(void)
{ delete[] myMake; }

virtual void Describe(void)
{
cout << "Car (" << myMake
<< "), registration "
<< myRegNum << endl;
}

protected:
char* myMake;
};

Vehicle* vp1 = new Car ("Jaguar","XJS 012");
Vehicle* vp2 = new Vehicle ("SGI 987");
Vehicle* vp3 = new Vehicle ("ABC 123");

vp1->Describe(); // PRINTS "Car (Jaguar)....."
vp2->Describe(); // PRINTS "Unknown vehicle....."
vp3->Describe(); // PRINTS "Unknown vehicle....."

How virtual functions¤ work

  • Normally when the compiler sees a member¤ function call it simply inserts instructions calling the appropriate subroutine (as determined by the type of the pointer or reference¤)
  • However, if the function is virtual a member¤ function call such as vp1->Describe() is replaced with following:
    (*((vp1->_vtab)[0]))()
  • The expression vp1->_vtab locates a special "secret" data member ¤¤ of the object pointed to by vp1. This data member¤¤ is automatically present in all objects with at least one virtual function¤. It points to a class¤-specific table of function pointers (known as the classe's vtable)
  • The expression (vp1->_vtab)[0] locates the first element of the object's class¤'s vtable (the one corresponding to the first virtual function¤ - Describe()). That element is a function pointer to the appropriate Describe() member¤ function.
  • Finally, the expression (*((vp1->_vtab)[0]))() dereferences¤ the function pointer and calls the function
  • We can visualize the set-up for vp1, vp2, and vp3 as follows:

        [Diagram showing the pointers vp1, vp2, and vp3, all pointing to objects          containing vtable pointers.  These pointers point at separate          class-specific vtables (one for Vehicle, one for Car).  The first          element of each vtable points at the corresponding Describe() member          function.]


Virtual destructors¤¤

  • The same problem that occurred with the non-virtual version of Vehicle::Describe() also occurs with the Vehicle::~Vehicle() destructor¤
  • That is, the following code doesn't call the Car::~Car() destructor¤, and so the memory allocated to the myMake data member¤¤ leaks ¤:
    Vehicle* vp4 = new Car ("Aston Martin", "JB 007");

    // AND LATER...

    delete vp4;
  • Why doesn't delete call the correct destructor¤?
  • We can force delete to call the destructor¤ corresponding to the type of the object (not that of the pointer), by declaring the Vehicle::~Vehicle() destructor¤ virtual:
    class Vehicle
    {
    public:
    Vehicle(char* regnum)
    : myRegNum(strdup(regnum))
    {}

    virtual ~Vehicle(void)
    { delete[] myRegNum; }

    virtual void Describe(void)
    {
    cout << "Unknown vehicle, registration "
    << myRegNum << endl;
    }

    protected:
    char* myRegNum;
    };

    class Car : public Vehicle
    {
    public:
    Car(char* make, char* regnum)
    : Vehicle(regnum), myMake( strdup(make) )
    {}

    virtual ~Car(void)
    { delete[] myMake; }

    virtual void Describe(void)
    { cout << "Car (" << myMake
    << "), registration "
    << myRegNum << endl;
    }

    protected:
    char* myMake;
    };

    Vehicle* vp4 = new Car ("Jaguar","XJS 012");

    delete vp4; // CALLS Car::~Car()
    // (WHICH THEN CALLS Vehicle::~Vehicle())


Reading

  • Lippman & Lajoie
  • New: Chapter 17.

  • Stroustrup
  • New: Chapter 12.

This material is part of the CSE2305 - Object-Oriented Software Engineering course.
Copyright © Jon McCormack & Damian Conway, 1998–2005. All rights reserved.

No comments: