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? Clearly the situation is undesirable, we don't what our tracks to develop a split personality! Instead we want embedded Track objects to report Propagate messages to the ChargedTrack object. This is possible by adding the one word "virtual" to the declaration of Track::Propagate (i.e. the Propagate owned by Track):-
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.

An Example of Polymorphism

A dramatic example of polymorphism can be found in the ROOT class TObject. This class occupies the base of a very deep inheritance tree. It has a Draw member function that inheriting classes redefine. Asking an embedded TObject to draw itself could produce almost anything: a histogram, an event display, or whatever graphically represents an object, polymorphism indeed!
Go Back to the The C++ Crib Top Page


If you have any comments about this page please send them to Nick West