Skip to main content

Section 19.9 Abstract Methods

Sometimes, it is useful to list a function in a base class even if there is no way to implement it there. Consider a set of classes representing different geometric objectsβ€”Circle, Rectangle, etc. We would like to unite them under a single base class so that we can define common behaviors and write code that works with any possible GeometricObject.
Circle and Rectangle both inherit from GeometricObject
---
config:
  layout: elk
  class:
    hideEmptyMembersBox: true
---
classDiagram
  class Circle {
  }
  class Rectangle {
  }
  class GeometricObject {
  }
  GeometricObject <|-- Circle
  GeometricObject <|-- Rectangle
        
      
Figure 19.9.1. UML for GeometricObjects
One thing we know every GeometricObject should be able to do is .getArea(). For mysteryObject.getArea() to work, we need to declare getArea() in GeometricObject and then override it in the derived classes. But what should the base version do? How can we compute the area of a geometric object with no other information? We could throw an exception or return a default value, indicating that the area cannot be computed without specific details about the shape. But another approach is to make the function abstract .
An abstract method (also known as a pure virtual function in C++) is a method that is declared without an implementation. It is essentially a promise that every GeometricObject subclass will provide its own implementation of the method, even though GeometricObject itself does not. In UML, we typically indicate abstract methods with italics:
Circle and Rectangle both inherit from GeometricObject
classDiagram
  class Circle {
    +getArea() double
  }
  class Rectangle {
    +getArea() double

  }
  class GeometricObject {
    +getArea()* double
  }
  GeometricObject <|-- Circle
  GeometricObject <|-- Rectangle
        
      
Figure 19.9.2. UML for GeometricObjects
In C++, we declare a method as abstract by using the virtual keyword followed by 0 in the method declaration. Something like: virtual void getArea() const = 0; Think of the = 0; as saying β€œthere is no implementation at this level”. Child classes will provide implementations instead of saying = 0;. In the GeometricObject class, this will look like:
Listing 19.9.3. GeometricObject.cxx
module;

#include <format>
#include <string>

export module GeometricObject;

using namespace std;

export class GeometricObject {
protected:
    string m_color;
    
public:
    // Constructor
    GeometricObject(string color);
    
    // getArea is an abstract function
    //   we have no way to do it at the base class level
    virtual double getArea() const = 0;
    
    // getColor is a regular virtual function
    //   it does not necessarily need to be virtual, it is unlikely
    //   to be overridden in derived classes
    virtual string getColor() const;
    
    // toString is a regular virtual function
    //   we can do the job here, but derived classes will likely want to override
    //   it to provide more specific information
    virtual string toString() const;
};

GeometricObject::GeometricObject(string color)
{
    this->m_color = color;
}

// getArea is abstract... nothing to list!

// note we do not use virtual outside the class
string GeometricObject::getColor() const
{
    return m_color;
}

string GeometricObject::toString() const
{
    return format("A {} object.", m_color);
}
Note that getColor and toString are regular virtual functions. We can do those jobs in the base class, so there is no need to make the abstract.
Now, when we go to implement the derived classes, we will need to provide specific implementations for the getArea method, ensuring that each geometric shape calculates its area correctly. Here is what Rectangle will look like:
Listing 19.9.4. Rectangle.cxx
module;

#include <format>
#include <string>

import GeometricObject;

export module Rectangle;

using namespace std;

export class Rectangle : public GeometricObject {
private:
    double m_width;
    double m_height;

public:
    Rectangle(double width, double height, string color);

    // provide an implementation of the getArea function
    //  it is no longer abstract
    virtual double getArea() const;

    // override the getStr function
    virtual string toString() const;
};

Rectangle::Rectangle(double width, double height, string color)
  : GeometricObject(color) {
    m_width = width;
    m_height = height;
}

double Rectangle::getArea() const {
    return m_width * m_height;
}

string Rectangle::toString() const {
    return format("A {} rectangle with dimensions of {} by {}.", 
                  getColor(),
                  m_width,
                  m_height);
}
Circle will look just like Rectangle, only it will do different math in getArea() and print a different message in toString(). So we will omit a full listing here and leave that for the end of the page (ListingΒ 19.9.6). Now we can use these classes:
Listing 19.9.5.

Insight 19.9.1.

The β€œwin” here is code that can ask any geometric object for its area, even though we don’t know how that possibly works in a general way.
If there was no getArea() in GeometricObject, we would not be able to call g.getArea() in checkObject.
An abstract (pure virtual) function promises β€œWe don’t know how to do that job here, but we guarantee that derived classes will implement this.”

Checkpoint 19.9.1.

Which are the true statements about abstract methods?
  • You declare them with an empty body. { }
  • Incorrect. They should not have a body.
  • You declare them with = 0;.
  • Abstract methods are a promise that derived classes will provide implementations.
  • You declare them with = 0;.
  • Abstract methods allow other code to assume that the method will exist in any object that has an is-a relationship with the class that defines them.
  • Abstract methods must also be virtual in C++.
  • Abstract methods can not be overriden.
  • Incorrect. Child classes MUST override abstract methods to provide implementations.
Here is the full listing for the Circle class:
Listing 19.9.6. Circle.cxx
module;

#include <format>
#include <string>

import GeometricObject;

export module Circle;

using namespace std;

export class Circle : public GeometricObject {
private:
    double m_radius;

public:
    Circle(double radius, string color);

    // same idea as Rectangle, but we will do different math
    double getArea() const;

    virtual string toString() const;
};

Circle::Circle(double radius, string color)
    : GeometricObject(color) {
    m_radius = radius;
}

double Circle::getArea() const {
    return std::numbers::pi * m_radius * m_radius;
}

string Circle::toString() const {
    return format("A {} circle with a radius of {}.", m_color, m_radius);
}
You have attempted of activities on this page.