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 seems 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; // Relations between clases (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 on how to take advantage os this powerful feature of C++. 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 << sqre.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, that 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 the members that CRectangle and CTriangle inherit from CPolygon. For that reason when calling to the area() members we have not been able to use them (*ppoly1 and *ppoly2).

So for that pointers to class CPolygon 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 would be because instead of calling to 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 the member of a derived class with the same name that one of the base class is suitably called when a pointer to it is used. In fact what happens is that that base class member is placed virtually in it, since in derived classes can be replaced, as it is the case in our example.

Observe as in spite of its virtuality we have also been able to declare a object of type CPolygon and to call to its area() function, that anyway always returns 0 as 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 CPoligon
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 receives the name of pure virtual function, and all classes that contains any pure virtual function are considered abstract base classes.

The greater difference of an abstract base class is that there cannot be created instances (objects) of it. 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 which of the derived classes is.

// ejemplo miembros virtuales
#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 a so 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-2001 - All rights reserved

Previous:
4-3. Relations between classes.

index
Next:
5-1. Templates.