Polymorphism.

Overriding methods and name hiding.

We have seen in previous lectures that we can overload methods or functions. In other words, we saw that we can define a number function with the same name but different signatures (function name + number of parameters and their data types) inside one class. After we learned about inheritance the next question arises: "What if we define a function with the same signature as a function in the base class inside the derived class?". For example, we can create the following two classes:
class Person {
   protected:
      string name;
   public:
      Person() : name("unknown") {};
      Person(char *n) : name(n) {};
      void Print() const { cout<<name; };
};

class Student : public Person {
   private:
      float gpa;
   public:
      Student() : gpa(0) {};
      Student(char *name, float GPA) : Person(name), gpa(GPA) {};
      void Print() const { cout<<gpa; };
};
As you can see the class Student has its own method Print() as well as the method Print() inherited from the base class Person. Obviously, these methods have the same signature. Would it generate an error? It turns out that not. This class definitions are absolutely fine, because the complete signatures of these methods are actually different. As a matter of fact the signature of the method Print() defined in the base class is
Person::Print(void)
as well as the complete signature of the Print() method of the derived class is
Student::Print(void)
Thus, we override the method Print() in the class Student.

The next question is: if we use the following code:

Student student("John Smith", 3.88);
student.Print();
which of these two methods will be invoked? The answer to this question will be - of course, the method of the Stident class since variable student is of the Student type. However, if we use the following code
Person *person = new Student("John Smith", 3.88);
person->Print();
where we treat an object of the Student class as an object of the class Person, we invoke the method of the Person class, since the variable person is a pointer to an object of the Person type.

Since, as we mentioned above, the signatures of the two Print() methods are different, we did not remove the method Person::Print() from the Student class, we just hid it. In other words, we still can call it, but we need to specify the complete name of the method (including the class name). For example, to invoke method Print() of the class Person for an object of the Student class, we need to do this:

Student student("John Smith", 3.88);
student.Person::Print();   // print the name of the student
student.Print();           // print the GPA of the student
We can use the complete name of the method inside methods of the class Student. For example, we can modify the Print() method of the Student class to print both name and GPA:
void Print() const { Person::Print();  cout<<": "<<gpa; };
The instruction Person::Print() invokes the method Print() of the base class.

In MS Visual C++ you can use the keyword __super (two underscore symbols) to refer to the parent (base) class. For example, the same modification can be done in MS C++ as

void Print() const { __super::Print();  cout<<": "<<gpa; };

If we use a pointer to a base class to store a pointer of a derived class, then we can still access the methods of the derived class explicitly, but in order to do so, we have to convert the pointer to the right type of pointer first:

Person *person = new Student("Bob Tester", 3.90);
person->Print();                        // invoking Person::Print()
static_cast<Student*>(person)->Print(); // invoking Student::Print()

Similarly, we can hide data members of the base class by defining data members with the same names in the derived class. To access members of the base class, we need to use their complete signatures. For example,

student.Person::public_data_member

Dynamic binding and virtual methods.

As you know we can have overloaded methods. For example, we can overload method Print() for the class Student:
void Student::Print()
{
   Person::Print();
   cout<<": "<<gpa;
}

void Student::Print(string msg)
{
   cout<<msg;
   Print();
}
You can notice that these methods have different signatures. Thus, when a compiler sees a method call:
student.Print("\n");
it knows that it should invoke method Student::Print(string). Thus, the function to call is determined when the program is compiled. The call is said to be statically bounded to the method Print. In other words, we had two candidate methods to call and the binding was the deciding which one of them to call.

If we use a code like

Person *p = new Student("John Tester", 3.90);
p->Print();
we by default using the static binding and because of this we use the method Person::Print(string). On the other hand, the pointer p is actually a pointer to an object of the class Student and we would like to invoke method Student::Print(string) instead. However, this cannot be done using the static binding, since the actual object is not created till the run-time. In other words, we would like to tell the compiler that we want it to use dynamic binding; that is, not to determine the exact method to call till the run-time, and during the run-time pick one of the candidates based on the exact type of the object. This can be done by specifying that the method Person::Print(string) is a virtual method:
class Person {
   protected:
      string name;
   public:
      Person() : name("unknown") {};
      Person(char *n) : name(n) {};
      virtual void Print() const { cout<<name; };
};
When a C++ compiler sees a virtual function it knows that it needs to use dynamic binding. A class that contains at least one virtual function is call a polymorphic class.
Polymorphism

In object-oriented programming, polymorphism (from the Greek meaning "having multiple forms") is the characteristic of being able to assign a different meaning to a particular symbol or "operator" in different contexts.

For example, the plus sign (+) can operate on two objects such that it adds them together (perhaps the most common form of the + operation) or, as in boolean searching, a + can indicate a logical "and" (meaning that both words separated by the + operator must be present in order for a citation to be returned). In another context, the + sign could mean an operation to concatenate the two objects or strings of letters on either side of the + sign.

A given operator can also be given yet another meaning when combined with another operator. For example, in the C++ language, a "++" following a variable can mean "increment this value by 1". The meaning of a particular operator is defined as part of a class definition. Since the programmer can create classes, the programmer can also define how operators work for this class of objects; in effect, the programmer can redefine the computing language.


Sources: Robert Lafore, Object-Oriented Programming in C++, Waite Group Press, Corte Madera, CA, USA, (1995). Last update: December 6, 1999

Please modify the previous example to have method Print dynamically bounded and run the same code:

Person *p = new Student("John Tester", 3.90);
p->Print();
After making the function Person::Print() virtual this code will invoke the method Student::Print(string) since the variable p contains a pointer to an object of the Student type during the run-time.

However, if we decide to modify class Student and add method PrintAll defined as:

void PrintAll() const { Person::Print(); cout<<": "<<gpa; };
then the following code
Person *p = new Student("John Tester", 3.90);
p->PrintAll();
will generate an error during the compiling:
filename.cpp(line#) : error C2039: 'PrintAll' : is not a member of 'Person'
This happens because the compiler cannot find a method PrintAll in the class Person (recall, that in the compilation time p is nothing but a pointer to a class Person). To avoid this kind of errors we can declare an virtual method PrintAll of the class Person with an empty body:
class Person {
   ...
   public:
      ...
      virtual void PrintAll() const {};
      ...
};
Please find the complete example on the example page.

Virtual destructors.

We have seen in the previous lecture that a destructor of the base class is called automatically when an object of a derived class is being destroyed. We saw an example that shows how it happens. However, it doesn't always behave like this. For example, if we have a base class Person and a derived class Student and we create an instance of the class Student dynamically:
Person *person = new Student("John Smith", 3.95);
What happens if we delete the object pointed at by the variable person?
delete person;
It turns out that only the destructor of the Person class will be executed. To be more specific, the following classes
class Person {
   protected:
      string name;
   public:
      ...
      ~Person() { cout<<"Destroyng object Person\n"; };
};

class Student : public Person {
   private:
      float gpa;
   public:
      ...
      ~Student() {  cout<<"Destroying object Student\n";  };
};
will generate the output:
Destroying object Person
This happens because person is a pointer to an object of the class Person and using delete person we deleting an object of the type Person, i. e. invoking the destructor of the Person class. To let the compiler know that we would like to use dynamic binding or, in other words, first figure out what object we actually is about to destroy and then call the appropriate destructor, we need to make the destructor of the base class virtual. That is, if we define the destructor of the class Person as
      virtual ~Person() { cout<<"Destroyng object Person\n"; };
Then the output will look like:
Destroying object Student
Destroying object Person
This happens because the variable person will be identified as a pointer to an object of the type Student, the appropriate constructor Student::~Student() will be called, and after finishing the body of the destructor the destructor of the base class Person::~Person() will be invoked.
We would recommend always declare desructors of base classes as virtual.

As you understand constructors cannot be virtual simply because when we create a new object we know exactly what kind of object we create.

Abstract classes.

An abstract class is a class that can only be a base class for other classes. In other words, we cannot create an instance of an abstract class. This kind of classes usually created to provide a common interface for all of its derived classes. A class is said to be abstract if it contains at least one pure virtual function. A pure virtual function is a virtual function that has a pure-specifier, designated by the =0 following the function prototype:
 virtual return_type function_name(parameter_list) = 0;

For example, we can design an abstract class for geometric figures. In this class we gather all common characteristics of all figures as well as their common methods (interface):

class Figure {
   protected:
      int x, y;
      static Graphics *rg;
      static Pen *erasor;
   public:
      Figure() : x(0), y(0) {};
      Figure(int X, int Y) : x(X), y(Y) {};
      virtual ~Figure() {};
      void MoveTo(int X, int Y) { x=X; y=Y; };
      virtual void Draw(Pen *pen) const = 0;
      virtual void Hide() const = 0;
      virtual void DrawFilled(Brush *bsrush) const = 0;
      ...
};
Using this class as a base class we can create derived classes Circle, Square, and Rectangle.
class Circle : public Figure {
   private:
      int radius;
   public:
      Circle() : radius(10) {};
      Circle(int x, int y, int r) : Figure(x, y), radius(r) {};
      ~Circle() {};
      void Draw(Pen *pen) const
      {
         gr->DrawEllips(pen, x, y, radius, radius);
      };
      void Hide() const
      {
         gr->DrawEllips(erasor, x, y, radius, radius);
      };
      void DrawFilled(Brush *bsrush) const
      {
         gr->FillEllips(brush, x, y, radius, radius);
      };
};
Please find the complete project example on the example page.

Run-time typing information.

Sometimes we need to retrieve information about the type of an object during the run-time. There are two ways of doing this. The first way is to use typeid() operator. This operator takes one argument which is either an object or a class name. As a result it returns an object of the type_info class. For example, we can find out if an object obj is of the Square data type:
Figure *obj = new Circle(5, 5, 10);
if( typeid(*obj) == typeid(Square) ) cout<<"Yes";
else cout<<"No";
Please note that we need to pass the object itself to the typeid() operator, not a pointer to an object. Among other things, the type_info object contains method name() that returns a string with the name of the class. For example, the following code will print exact data types of all objects in the polymorphic array pieces:
std::vector<Figure*> pieces;
// randomly initialize them
...
for(i=0;i<pieces.size();i++)
   cout<<typeid( *(pieces[i]) ).name()<<endl;
The type_info type is defined in the <typeinfo> header file. Please study the complete program example.

Another way to figure out if an object pointer points to an object of a specific type is to use the dynamic_cast operator. This operator takes the type you want to convert a variable into as a template argument (embraced by < and > signs) and the object to convert as a parenthesized argument. For example:

Figure *f = new Square(5, 5, 10);
Square *s = dynamic_cast<Square*>( f );
If the dynamic type of the parameter matches the template argument type, the value is returned. If the type is not correct, them the NULL value is returned. We can compare the result with the NULL value to see if the conversion was successful. For example, we can find out if variable f contains a pointer to an object of the Rect type:
Rect *r = dynamic_cast<Rect*>( f );
if( r != NULL )
   cout<<"This is not a rectangle";

The static_cast operator is similar, but performs no run-time checks on the result. If the argument cannot be converted to the specified type, no indication will be given. Most of the time you won't even be able to compile the program due to an error message like:

Error E2031 cast.cpp 70: Cannot cast from 'C *' to 'B *' in function main()
*** 1 errors in Compile ***
However, even if you manage to compile the program, the result of impossible static casting will be unpredictable. For this reason the static_cast should be used for primitive data types or not polymorphic types. Please find the compete example on the example page.

At the end of the chapter we would like to note that only pointers and references are polymorphic in C++, the real objects are not. For example, if we have two object (not references) declared as

Student student("John Smith", 4.0);
Person  person;
we can use the following assignment (since Student is a Person):
person = student;
but all additional fields of the student object will be cut, and if we use
person.print();
then Person::print() will be invoked despite it declared as virtual. Please find the compete example on the example page.

Homework assignment

There are several issues about virtual functions and inheritance we haven't discussed in the lectures. I would like you to study one of them as a home work assignment. Assume you have a base class Figure and a derived class Square, also assume that both of these classes have methods Display() and Show() defined, but the first method (Display()) is not virtual and the other (Show()) is. ALso assume that method Display() calls Show():
class Figure {
   ...
   public:
      void Display() { Show(); };
      virtual void Show() { ... };
};

class Square : public Figure {
   ...
   public:
      void Display() { Show(); };
      virtual void Show() { ... };
}:
Please answer the questions about the following code:
Figure *fig = new Square();
fig->Display();
dynamic_cast<Square*>(fig)->Display();