Abstract Classes and Pure Virtual Functions in C++

An abstract class cannot be used to create objects. An abstract class can contain abstract functions, which are implemented in concrete derived classes.

In the inheritance hierarchy, classes become more specific and concrete with each new derived class. If you move from a derived class back up to its parent and ancestor classes, the classes become more general and less specific. Class design should ensure that a base class contains common features of its derived classes. Sometimes a base class is so abstract that it cannot have any specific instances. Such a class is referred to as an abstract class.

In Section 15.2, GeometricObject was defined as the base class for Circle and Rectangle. GeometricObject models common features of geometric objects. Both Ci rcl e and Rectangl e contain the getArea() and getPerimeter() functions for com­puting the area and perimeter of a circle and a rectangle. Since you can compute areas and perimeters for all geometric objects, it is better to define the getArea() and getPerime- ter() functions in the GeometricObj ect class. However, these functions cannot be imple­mented in the GeometricObject class, because their implementation is dependent on the specific type of geometric object. Such functions are referred to as abstract functions. After you define the abstract functions in GeometricObject, GeometricObject becomes an abstract class. The new GeometricObject class is shown in Figure 15.2. In UML graphic notation, the names of abstract classes and their abstract functions are italicized, as shown in Figure 15.2.

In C++, abstract functions are called pure virtual functions. A class that contains pure vir­tual functions becomes an abstract class. A pure virtual function is defined this way:

The = 0 notation indicates that getArea is a pure virtual function. A pure virtual function does not have a body or implementation in the base class.

Listing 15.13 defines the new abstract GeometricObject class with two pure virtual functions in lines 18-19.

Listing 15.13 AbstractGeometricObject.h

1 #ifndef GEOMETRICOBJECT_H
2
#define GEOMETRICOBJECT_H
3
#include <string>
4
using namespace std;
5
6
class GeometricObject
7 {
8     
protected:
9     GeometricObject();
10    GeometricObject(
const string& color, bool filled);
11
12
public:
13    string getColor()
const;
14   
void setColor(const string& color);
15   
bool isFilled() const;
16   
void setFilled(bool filled);
17    string toString()
const;
18   
virtual double getArea() const = 0;
19   
virtual double getPerimeter() const = 0;
20
21
private:
22    string color;
23   
bool filled;
24 };
// Must place semicolon here
25
26
#endif

Figure 15.2 The new GeometricObject class contains abstract functions

GeometricObject is just like a regular class, except that you cannot create objects from it because it is an abstract class. If you attempt to create an object from GeometricObject, the compiler will report an error.

Listing 15.14 gives an implementation of the GeometricObject class.

Listing 15.14 AbstractGeometricObject.cpp

1 #include “AbstractGeometricObject.h”
2
3 GeometricObject::GeometricObject()
4 {
5    color =
“white”;
6    filled =
false;
7 }
8
9 GeometricObject::GeometricObject(
const string& color, bool filled)
10 {
11    setColor(color);
12    setFilled(filled);
13 }
14
15 string GeometricObject::getColor()
const

16 {
17   
return color;
18 }
19
20
void GeometricObject::setColor(const string& color)
21 {
22   
this->color = color;
23 }
24
25
bool GeometricObject::isFilled() const
26 {
27   
return filled;
28 }
29
30
void GeometricObject::setFilled(bool filled)
31 {
32   
this->filled = filled;
33 }
34
35 string GeometricObject::toString()
const
36 {
37   
return “Geometric Object”;
38 }

Listings 15.15, 15.16, 15.17, and 15.18 show the files for the new Circle and Rectangle classes derived from the abstract GeometricObject.

Listing 15.15 DerivedCircleFromAbstractGeometricObject.h

1 #ifndef CIRCLE_H
2
#define CIRCLE_H
3
#include “AbstractGeometricObject.h”
4
5
class Circle: public GeometricObject
6 {
public:
8     Circle();
9     Circle(
double);
10    Circle(
double radius, const string& color, bool filled);
11   
double getRadius() const;
12   
void setRadius(double);
13   
double getArea() const;
14   
double getPerimeter() const;
15   
double getDiameter() const;
16
17
private:
18   
double radius;
19 };
// Must place semicolon here
20
21
#endif

Listing 15.16 DerivedCircleFromAbstractGeometricObject.cpp

1 #include “DerivedCircleFromAbstractGeometricObject.h”
2
3
// Construct a default circle object
4 Circle::Circle()

5 {
6    radius =
1;
7 }
8
9
// Construct a circle object with specified radius
10 Circle::Circle(double radius)
11 {
12    setRadius(radius);
13 }
14
15
// Construct a circle object with specified radius, color, filled
16 Circle::Circle(double radius, const string& color, bool filled)
17 {
18    setRadius(radius);
19    setColor(color);
20    setFilled(filled);
21 }
22
23
// Return the radius of this circle
24 double Circle::getRadius() const
25 {
26   
return radius;
27 }
28
29
// Set a new radius
30 void Circle::setRadius(double radius)
31 {
32   
this->radius = (radius >= 0) ? radius : 0;
33 }
34
35
// Return the area of this circle
36 double Circle::getArea() const
37 {
38   
return radius * radius * 3.14159;
39 }
40
41
// Return the perimeter of this circle
42 double Circle::getPerimeter() const
43 {
44   
return 2 * radius * 3.14159;
45 }
46
47
// Return the diameter of this circle
48 double Circle::getDiameter() const
49 {
50   
return 2 * radius;
51 }

Listing 15.17 DerivedRectangleFromAbstractGeometricObject.h

1 #ifndef RECTANGLE_H
2
#define RECTANGLE_H
3
#include “AbstractGeometricObject.h”
4
5
class Rectangle: public GeometricObject
6 {
7
public:
8     Rectangle();

9     Rectangle(double width, double height);
10    Rectangle(
double width, double height,
11   
const string& color, bool filled);
12   
double getWidth() const;
13   
void setWidth(double);
14   
double getHeight() const;
15   
void setHeight(double);
16   
double getArea() const;
17   
double getPerimeter() const;
18
19
private:
20   
double width;
21   
double height;
22 };
// Must place semicolon here
23
24
#endif

Listing 15.18 DerivedRectangleFromAbstractGeometricObject.cpp

1 #include “DerivedRectangleFromAbstractGeometricObject.h”
2
3
// Construct a default retangle object
4 Rectangle::Rectangle()
5 {
6    width =
1;
7    height =
1;
8 }
9
10
// Construct a rectangle object with specified width and height
11 Rectangle::Rectangle(double width, double height)
12 {
13    setWidth(width);
14    setHeight(height);
15 }
16
17
// Construct a rectangle object with width, height, color, filled
18 Rectangle::Rectangle(double width, double height,
19
const string& color, bool filled)
20 {
21    setWidth(width);
22    setHeight(height);
23    setColor(color);
24    setFilled(filled);
25 }
26
27
// Return the width of this rectangle
28 double Rectangle::getWidth() const
29 {
30   
return width;
31 }
32
33
// Set a new radius
34 void Rectangle::setWidth(double width)
35 {
36   
this->width = (width >= 0) ? width : 0;
37 }
38
39
// Return the height of this rectangle

40 double Rectangle::getHeight() const
41 {
42   
return height;
43 }
44
45
// Set a new height
46 void Rectangle::setHeight(double height)
47 {
48   
this->height = (height >= 0) ? height : 0;
49 }
50
51
// Return the area of this rectangle
52 double Rectangle::getArea() const
53 {
54   
return width * height;
55 }
56
57
// Return the perimeter of this rectangle
58 double Rectangle::getPerimeter() const
59 {
60   
return 2 * (width + height);
61 }

You may be wondering whether the abstract functions getArea and getPerimeter should be removed from the GeometricObject class. The following example in Listing 15.19 shows the benefits of defining them in the GeometricObject class.

This example presents a program that creates two geometric objects (a circle and a rectan­gle), invokes the equalArea function to check whether the two objects have equal areas, and invokes the displayGeometricObject function to display the objects.

Listing 15.19 TestAbstractGeometricObject.cpp

1 #include “AbstractGeometricObject.h”
2 #include “DerivedCircleFromAbstractGeometricObject.h”
3 #include “DerivedRectangleFromAbstractGeometricObject.h”
4 #include <iostream>
5
using namespace std;
6
7
// A function for comparing the areas of two geometric objects
8 bool equalArea(const GeometricObject& g1,
9
const GeometricObject& g2)
10 {
11   
return g1.getArea() == g2.getArea();
12 }
13
14
// A function for displaying a geometric object
15 void displayGeometricObject(const GeometricObject& g)
16 {
17    cout <<
“The area is ” << g.getArea() << endl;
18    cout <<
“The perimeter is ” << g.getPerimeter() << endl;
19 }
20
21
int main()
22 {
23    Circle circle(
5);
24    Rectangle rectangle(
5, 3);
25
26    cout <<
“Circle info: ” << endl;
27    displayGeometricObject(circle);

28
29    cout <<
“\nRectangle info: ” << endl;
30    displayGeometricObject(rectangle);
31
32    cout <<
“\nThe two objects have the same area? ” <<
33    (equalArea(circle, rectangle) ?
“Yes” : “No”) << endl;
34
35   
return 0;
36 }

The program creates a Circle object and a Rectangle object in lines 23-24.

The pure virtual functions getArea() and getPerimeter() defined in the GeometricObject class are overridden in the Circle class and the Rectangle class.

When invoking displayGeometricObject(circle1) (line 27), the functions getArea and getPerimeter defined in the Circle class are used, and when invok­ing displayGeometricObject(rectangle) (line 30), the functions getArea and getPerimeter defined in the Rectangle class are used. C++ dynamically determines which of these functions to invoke at runtime, depending on the type of object.

Similarly, when invoking equalArea(circle, rectangle) (line 33), the getArea function defined in the Circle class is used for g1.getArea(), since g1 is a circle. Also, the getArea function defined in the Rectangle class is used for g2.getArea(), since g2 is a rectangle.

Note that if the getArea and getPerimeter functions were not defined in GeometricObject, you cannot define the equalArea and displayObject func­tions in this program. So, you now see the benefits of defining the abstract functions in GeometricObject.

Source: Liang Y. Daniel (2013), Introduction to programming with C++, Pearson; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *