Topic 10: C++ Polymorphism
Synopsis
- Pointers, references¤ and inheritance
- Polymorphism¤ in C++
- Virtual functions¤
- An example
- How virtual functions¤ work
- Virtual destructors¤¤
- Reading
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*
orVehicle&
in both cases), rather than the type of the object being pointed at or referred to (Vehicle
orCar
)
- So all the above calls to a
Describe()
member¤ are dispatched toVehicle::Describe()
, even when the pointer of reference¤ actually points to aCar
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 byvp1
. 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 appropriateDescribe()
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
, andvp3
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())
Reading
- Lippman & Lajoie
- New: Chapter 17.
- Stroustrup
- New: Chapter 12.
No comments:
Post a Comment