6.3. Derivation from an Abstract Base Class

[ fromfile: inheritance-intro.xml id: abstractbaseclass ]

Figure 6.2.  Animal Taxonomy

Animal Taxonomy

Consider Figure 6.2, which shows an inheritance diagram of a tiny portion of the animal kingdom. We use it to explain the difference between an abstract and a concrete class. An abstract base class is used to encapsulate common features of concrete derived classes. An abstract class cannot be instantiated. Nevertheless, this scheme is quite useful and efficient for organizing the accumulated knowledge of the vastly complex biological world. For example, a primate is a mammal that has certain additional characteristics, a hominid is a particular kind of primate, a gorilla is a hominid with certain additional characteristics, and so forth.

A concrete class represents a particular kind of entity, something that really exists (i.e., can be instantiated). For example, when walking through the woods you never encounter a real, live animal that is completely described by the designation, Carnivora, or Felidae. You may, depending on where you walk, find a lion, a siamese cat, or a common housecat (Felis silvestris). But there is no instance of a Hominidae (i.e., of a base class) in the concrete world that is not also an instance of some particular species. If a biologist ever finds a concrete instance that does not fit into an existing species definition, then that biologist may define and name a new species and become famous.

To summarize, the more general categories (class, order, family, subfamily) are all abstract base classes that cannot be instantiated in the concrete world. They were invented by people to help with the classification and organization of the concrete classes (species).

Back to Programming .  At first, it might seem counterintuitive to define a class for an abstract idea that has no concrete representative. But classes are groupings of functions and data and are useful tools to enable certain kinds of organization and reuse. Categorizing things makes the world simpler and more manageable for humans and computers.

As you study design patterns and develop frameworks and class libraries, you will often design inheritance trees where only the leaf nodes (those with no children) can be instantiated, and the inner nodes are mostly abstract.

An abstract base class is a class that is impossible or inappropriate to instantiate. Features of a class that tell the compiler to enforce this rule are

Now look at an example of an abstract Shape class that has pure virtual functions.

Figure 6.3.  Shapes UML Diagram

Shapes UML Diagram

The names of abstract classes are italicized in UML diagrams.

A pure virtual function has the following declaration syntax:

    virtual returnType
      functionName(parameterList)=0;
    

Example 6.13 shows the base class definition.

Example 6.13. src/derivation/shape1/shapes.h

[ . . . . ]

class Shape {                           1
 public:
    virtual double area() = 0;          2
    virtual QString getName() = 0;
    virtual QString getDimensions() = 0;
    virtual ~Shape() {}
};

1

An abstract base class.

2

Pure virtual function.

<include href="src/derivation/shape1/shapes.h" segid="shape" src="src/derivation/shape1/shapes.h" mode="cpp" id="shapeh"/>


getName(), area(), and getDimensions() are all pure virtual functions. Because they are defined to be pure virtual, no function definition is required in the Shape class. Any concrete derived class must override and define all pure virtual base class functions for instantiation to be permitted. In other words, any derived class that does not override and define all pure virtual base class functions is, itself, an abstract class. Example 6.14 shows the derived class definitions.

Example 6.14. src/derivation/shape1/shapes.h

[ . . . . ]

class Rectangle : public Shape {
 public:
    Rectangle(double h, double w) :
        m_Height(h), m_Width(w) {}
    double area();
    QString getName();
    QString getDimensions();

 protected:                             1
    double m_Height, m_Width;
};

class Square : public Rectangle {
 public:
    Square(double h)
       : Rectangle(h,h)                 2
    { }
    double area();
    QString getName();
    QString getDimensions();
};

class Circle : public Shape {
 public:
    Circle(double r) : m_Radius(r) {}
    double area();
    QString getName();
    QString getDimensions();
 private:
    double m_Radius;
};

1

We want to access m_Height in Square class.

2

Base class name in member initialization list - pass arguments to base class ctor.

<include href="src/derivation/shape1/shapes.h" segid="derived" src="src/derivation/shape1/shapes.h" mode="cpp" id="shapeh-derv"/>


Rectangle and Circle are derived from Shape. Square is derived from Rectangle. Their implementations are shown in Example 6.15.

Example 6.15. src/derivation/shape1/shapes.cpp

#include "shapes.h"
#include <math.h>
    double Circle::area() {
        return(M_PI * m_Radius * m_Radius); 1
    }

    double Rectangle::area() {
        return (m_Height * m_Width);
    }

    double Square::area() {
        return (Rectangle::area());         2
    }
[ . . . . ]

1

M_PI comes from <math.h>, the cstdlib include file.

2

Calling base class version on 'this'.

<include src="src/derivation/shape1/shapes.cpp" href="src/derivation/shape1/shapes.cpp" mode="cpp" id="shapescpp"/>


Example 6.16 provides some client code to exercise these classes.

Example 6.16. src/derivation/shape1/shape1.cpp

#include "shapes.h"
#include <QString>
#include <QDebug>

void showNameAndArea(Shape* pshp) {
    qDebug() << pshp->getName() 
             << " " << pshp->getDimensions() 
             << " area= " << pshp->area();
}

int main() {    
    Shape shp;                              1

    Rectangle  rectangle(4.1, 5.2);
    Square     square(5.1);
    Circle     circle(6.1);

    qDebug() << "This program uses hierarchies for Shapes";
    showNameAndArea(&rectangle);
    showNameAndArea(&circle);
    showNameAndArea(&square);
    return 0;
}

1

ERROR - instantiation is not allowed on classes with pure virtual functions.

<include src="src/derivation/shape1/shape1.cpp" href="src/derivation/shape1/shape1.cpp" mode="cpp" id="shapeclient"/>


In the global function showNameAndArea() the base class pointer, pshp, is successively given the addresses of objects of the three subclasses. For each address assignment, pshp polymorphically invokes the correct getName() and area() functions. Example 6.17 shows the output of the program.

Example 6.17. src/derivation/shape1/shape.txt

This program uses hierarchies for Shapes

 RECTANGLE  Height = 4.1 Width = 5.2   area = 21.32
 CIRCLE  Radius = 6.1   area = 116.899
 SQUARE  Height = 5.1    area = 26.01

<include src="src/derivation/shape1/shape.txt" href="src/derivation/shape1/shape.txt" mode="txt" id="shapes-output"/>