We began the chapter with three shape classes: Circle, Rectangle, and Triangle classes. Although we didn't write the code to draw the shapes, we can imagine, at least generally, how the shapes look. Given some specific information (e.g., a radius, width, or height), we could develop algorithms and write code to draw these shapes. Later, as one step toward implementing polymorphism, we created an inheritance hierarchy by adding a Shape class and making it a superclass of the other shapes. Imagine visualizing a Shape. How does it look? Does the Shape you visualize look like the Shape I visualize? More precisely, what code would we write for the Shape class's draw function? A Shape is too general or ambiguous - there are many different shapes - for us to draw or program.
"Normal" functions, including virtual
functions, must have a body (defined inline in the class or defined in an appropriate .cpp file). In the case of our shape classes, there must be algorithms describing how to draw each shape, and there must be code in the body of each shape draw function. But the Shape draw function presents us with a problem: it's too ambiguous to draw, so we can't write code in its draw function. But if we remove the draw function from the Shape class, we lose one requirement for polymorphism: overridden functions. We solve this problem by making the Shape draw function a pure virtual function.
class Shape { private: ... public: ... virtual void draw() = 0; };
= 0
, to the end of the prototype. If the Shape draw function doesn't have a body, then we don't need an algorithm to draw it, solving our problem. Making Shape draw a pure virtual function makes Shape an abstract class. Indeed, a C++ pure virtual function is, in object-speak, an abstract function. The distinction between an abstract and a concrete class, the kind that we have been building throughout the semester, is that we can instantiate a concrete class but not an abstract one.
We might ask, "If a function doesn't have a body, what good is it?" We can answer that question in two ways - ways that might seem different but are just other aspects of how C++ implements polymorphism.
switch (choice) { case 'c' : s = new Circle(); break; case 's' : s = new Rectangle(); break; case 't' : s = new Triangle(); break; } |
void render(Shape* s) { if (s != nullptr) s->draw(); else throw "Null pointer error"; } |
s->draw();
will find a draw function.We can force subclasses to have a particular function by including an appropriate pure virtual function in their superclass. A more extensive example will help us better understand the value of abstract functions.
Employee | SalariedEmployee | SalesEmployee |
---|---|---|
public: virtual double calc_pay() = 0; |
public: double calc_pay() { return salary / 24; } |
public: double calc_pay() { return SalariedEmployee::calc_pay() + commission; } |
(a) | (b) | (c) |
salary / 24
, where salary is a private member of the SalariedEmployee classSalariedEmployee::
is necessary to specify which calc_pay function to call. Without this notation, the compiler will create an erroneous recursive call to the SalesEmployee calc_pay functionThe previous example demonstrated a virtual
function, SalesEmployee::calc_pay(), chaining to the function it overrides, SalariedEmployee::calc_pay(). However, SalariedEmployee::calc_pay() cannot chain to Employee::calc_pay() because it doesn't have a body. C++ allows pure virtual functions to have executable bodies but requires programmers to separate the pure virtual specification from the body definition.
![]() |
class Shape { public: virtual void draw() = 0; // (a) }; void Shape::draw() // (b) { { cout << "Shape - "; } } |
class Circle : public Shape { public: virtual void draw() { Shape::draw(); // (c) cout << "Circle\n"; } }; |
|
class Rectangle : public Shape { public: virtual void draw() { Shape::draw(); // (c) cout << "Rectangle\n"; } }; |
class Triangle : public Shape { public: virtual void draw() { Shape::draw(); // (c) cout << "Triangle\n"; } }; |
Classes that have one or more pure virtual functions are said to be abstract. Until this section, all our class examples have been concrete. The difference between concrete and abstract classes is that a program may make an instance of a concrete class but not an abstract class. If a program can't have an instance of an abstract class, what good is the class? Although a program can't make an object from an abstract class, there are some things that an abstract class can still do: