OO Concept: Virtual Functions & Polymorphism
In the OO topic Inheritance the idea of developing one class (the subclass) by building on another (the base class) was introduced. This powerful technique allows common features of a system to be modeled (or abstracted) by generic classes, while more specific classes only have to describe how the more specialised objects differ from the general. There is no duplication of functionality, behaviour is distributed through the class inheritance tree, placing it at the head of the sub-tree that shares it. It was further explained how objects of subclasses had, within them, embedded base class objects that could be passed messages both from the subclass and externally.
We illustrated these features with 3 classes, the base class Track, and 2 subclasses, ChargedTrack and NeutralTrack. These track classes were part of a Monte Carlo and had a Propagate message, that transported them through the detector. We decided the neutral and charged track propagation were sufficiently different that the ChargedTrack class would have its own Propagate function, while the NeutralTrack class would just "inherit" the one from Track. The essential structure was:-
class Track {
public:
void Propagate();
...
};
class ChargedTrack: public Track {
public:
void Propagate();
...
};
class NeutralTrack: public Track {
...
};
ChargedTrack objects have 2 Propagate functions, for the embedded Track object
is alive and well and can respond to messages passed to it both internally and
externally. Consider:-
ChargedTrack *MyChargedTrack = new ChargedTrack(0.938, 1.0);
Track *MyTrack = MyChargedTrack;
...
MyChargedTrack->Propagate();
MyTrack->Propagate();
First we create a ChargedTrack object
and then derive a pointer to the embedded Track
object. Then we send the Propagate messages to both. What happens?
class Track {
public:
virtual void Propagate();
...
};
What happens is that, for any member function
that is declared as virtual, its address is added to virtual function address
table held inside the object. Any call to a virtual function involves two
steps, first to the table, and then from the table to the function. When a
Track object is created, the address of Track::Propagate is placed in the
table. However when a ChargedTrack object is created the table entry is
overwritten with ChargedTrack::Propagate. So now any user sending
messages whether to ChargedTrack, or to the embedded Track, ends up propagating
a charged track. Internally ChargedTrack member functions can still access
Track::Propagate, for when explicitly specifying a class like that, the
function table is bypassed.
It would be a serious mistake just to see this as a type of "bug fix" to stop people abusing objects by talking to other objects embedded within them. It is much more fundamental than that. Consider our Monte Carlo. Early on we would have started to develop some driver code that looks symbolically like:-
Track *MyTrack = GetFirstTrack();
while ( ! MyTrack ) {
MyTrack->Propagate();
MyTrack = GetNextTrack();
}
For now we won't worry how GetFirstTrack and GetNextTrack work, we will
consider this type of problem in the OO topic
Containers
suffice it to say this gives us access to all our tracks. The
while ( ! MyTrack ) {
business just ensures we loop until the
pointer
is zero
(i.e. a null pointer)
Later in our
development of the Monte Carlo we introduce the idea of ChargedTrack and
NeutralTrack. We now have a heterogeneous mixture of tracks and it looks like
our driver is going to need some rather ugly hacking to get it to work. But
not a bit of it, for within each track there is a Track object so we can
still view them at this level as homogeneous. All we have to do is to ensure
we always keep our tracks as a set of Track pointers and then the driver code is independent of track code development,
it doesn't
even have to be recompiled! We are now at the very heart of the OO
view of the world. Our driver knows about generic tracks and this
model has the
generic idea of propagation. That is all it needs to know; what propagation
means to a particular track can be left to the particular
object. We have started to build a
firewall between the driver, or user, and the object. This theme is further
explored in the OO topic
Interfaces & Abstract Classes.
It should now be apparent why this concept is called polymorphism. It allows an object to change its behaviour when embedded within another, the only requirement of good design is that its new behavior be consistent with, if rather more specific than, its original behaviour.