Section 4.4
Polymorphism
cplusplus.com

For a suitable understanding of this section you should clearly know how to use pointers and inheritance between classes. I recommend that if some of these expressions seem strange to you, you review the indicated sections:
  int a::b(c) {};    // Classes (Section 4.1)
  a->b               // pointers and objects (Section 4.2)
  class a: public b; // Relationships between classes (Section 4.3)

Pointers to base class

One of the greater advantages of deriving classes is that a pointer to a derived class is type-compatible with a pointer to its base class. This section is fully dedicated to taking advantage of this powerful C++ feature. For example, we are going to rewrite our program about the rectangle and the triangle of the previous section considering this property:

// pointers to base class
#include <iostream.h>

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = ▭
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << rect.area() << endl;
  cout << trgl.area() << endl;
  return 0;
}
20
10

The function main creates two pointers that point to objects of class CPolygon, that are *ppoly1 and *ppoly2. These are assigned to the addresses of rect and trgl, and because they are objects of classes derived from CPolygon they are valid assignations.

The only limitation of using *ppoly1 and *ppoly2 instead of rect and trgl is that both *ppoly1 and *ppoly2 are of type CPolygon* and therefore we can only refer to the members that CRectangle and CTriangle inherit from CPolygon. For that reason when calling the area() members we have not been able to use the pointers *ppoly1 and *ppoly2.

To make it possible for the pointers to class CPolygon to admit area() as a valid member, this should also have been declared in the base class and not only in its derived ones. (see the following section).

Virtual members

In order to declare an element of a class which we are going to redefine in derived classes we must precede it with the keyword virtual so that the use of pointers to objects of that class can be suitable.

Take a look at the following example:

// virtual members
#include <iostream.h>

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void)
      { return (0); }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon poly;
  CPolygon * ppoly1 = ▭
  CPolygon * ppoly2 = &trgl;
  CPolygon * ppoly3 = &poly;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly3->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  cout << ppoly3->area() << endl;
  return 0;
}
20
10
0

Now the three classes (CPolygon, CRectangle and CTriangle) have the same members: width, height, set_values() and area().

area() has been defined as virtual because it is later redefined in derived classes. You can verify if you want that if you remove this word (virtual) from the code and then you execute the program the result will be 0 for the three polygons instead of 20,10,0. That is because instead of calling the corresponding area() function for each object (CRectangle::area(), CTriangle::area() and CPolygon::area(), respectively), CPolygon::area() will be called for all of them since the calls are via a pointer to CPolygon.

Therefore what the word virtual does is to allow that a member of a derived class with the same name as one in the base class be suitably called when a pointer to it is used, as in the above example.

Note that in spite of its virtuality we have also been able to declare an object of type CPolygon and to call its area() function, that always returns 0 as the result.

Abstract base classes

Basic abstract classes are something very similar to the class CPolygon of our previous example. The only difference is that in our previous example we have defined a valid area() function for objects that were of class CPolygon (like object poly), whereas in an abstract base class we could have simply left without defining this function by appending =0 (equal to zero) to the function declaration.

The class CPolygon could have been thus:

// abstract class CPolygon
class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
  };
Notice how we have appended =0 to virtual int area (void) instead of specifying an implementation for the function. This type of function is called a pure virtual function, and all classes that contain a pure virtual function are considered abstract base classes.

The greatest difference of an abstract base class is that instances (objects) of it cannot be created, but we can create pointers to them. Therefore a declaration like:

CPolygon poly;
would be incorrect for the abstract base class declared above. Nevertheless the pointers:
CPolygon * ppoly1;
CPolygon * ppoly2
are be perfectly valid. This is because the pure virtual function that it includes is not defined and it is impossible to create an object if it does not have all its members defined. Nevertheless a pointer that points to an object of a derived class where this function has been defined is perfectly valid.

Here you have the complete example:

// virtual members
#include <iostream.h>

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = ▭
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  cout << ppoly1->area() << endl;
  cout << ppoly2->area() << endl;
  return 0;
}
20
10
If you review the program you will notice that we can refer to objects of different classes using a unique type of pointer (CPolygon*). This can be tremendously useful. Imagine, now we can create a function member of CPolygon that is able to print on screen the result of the area() function independently of what the derived classes are.

// virtual members
#include <iostream.h>

class CPolygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b; }
    virtual int area (void) =0;
    void printarea (void)
      { cout << this->area() << endl; }
  };

class CRectangle: public CPolygon {
  public:
    int area (void)
      { return (width * height); }
  };

class CTriangle: public CPolygon {
  public:
    int area (void)
      { return (width * height / 2); }
  };

int main () {
  CRectangle rect;
  CTriangle trgl;
  CPolygon * ppoly1 = ▭
  CPolygon * ppoly2 = &trgl;
  ppoly1->set_values (4,5);
  ppoly2->set_values (4,5);
  ppoly1->printarea();
  ppoly2->printarea();
  return 0;
}
20
10

Remember that this represents a pointer to the object whose code is being executed.

Abstract classes and virtual members grant to C++ the polymorphic characteristics that make object-oriented programming such a useful instrument. Of course we have seen the simplest way to use these features, but imagine these features applied to arrays of objects or objects assigned through dynamic memory.

© The C++ Resources Network, 2000-2003 - All rights reserved

Previous:
4-3. Relationships between classes.

index
Next:
5-1. Templates.