programming.nbk: Home | Index | Next Page: char* asctime(const struct tm* timeptr); | Previous Page: C++ Stream I/O


 C++ virtual functions

http://www.csse.monash.edu.au/~jonmc/CSE2305/Topics/05.10.VirtualFns/html/text.html

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:


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())

programming.nbk: Home | Index | Next Page: char* asctime(const struct tm* timeptr); | Previous Page: C++ Stream I/O


Notebook exported on Monday, 7 July 2008, 18:56:06 PM Eastern Daylight Time