C++. Abstract class. Pure virtual function

Abstract class. Pure virtual function. The abstract keyword. Examples

Before studying this topic, it is recommended that you familiarize yourself with the following topic:


Contents


Search other resources:

1. Pure virtual function. The concept

Before studying the concept of an abstract class, you should familiarize yourself with the concept of “pure virtual function”.

As you know, the use of virtual functions only makes sense if the classes form an inheritance hierarchy. If there is no class hierarchy, then the mechanism of virtual functions does not work. Class hierarchies are built on the principle from the general to the specific. This means that at the top levels of hierarchies, classes are declared that define something in common. With the advancement to the lower levels of the hierarchy, this common is detailed (specialized). The lower level classes contain concrete implementations.

Thus, often the virtual function code at the top of the class hierarchy is irrelevant, as it is detailed in lower-level classes. Only the organization of communication between classes of lower levels matters. In this case, the virtual functions declared at the top of the class hierarchies have only a bridging role. Therefore, there is a need to declare “empty” functions that do not contain code.

If we consider class hierarchies, then very many of them contain virtual functions that do not have code or do not have a function body. Sometimes classes at the top levels of hierarchies contain only empty virtual functions. In C++, a virtual function without code is called a pure virtual function.

In the syntax of the C++ language, it is possible to declare a pure virtual function. A pure virtual function is one that:

  • is declared in a class with the virtual specifier;
  • is declared using a special C++ syntax that defines a function as having no block of curly braces { } containing program code.

In the most general case, the declaration of a purely virtual function is:

class ClassName
{
  virtual return_type FuncNamePure(list_of_parameters) = 0;
};

where

  • FuncNamePure() – the name of a pure virtual function;
  • return_type – the type returned by the function;
  • list_of_parameters – list of function parameters.

It is important to understand that the above pure virtual function declaration should not be confused with a virtual function declaration containing an empty block of code.

...

// It is not a pure virtual function
virtual return_type FuncName(list_of_parameters)
{ }

...

 

2. Abstract class. General concepts. Features of the declaration and use

If the classes form a hierarchy, then the classes at the top of the hierarchy usually contain one or more virtual functions. These functions are used to coordinate the interaction of other classes placed at lower levels of the hierarchy. Most often, classes at the top of the hierarchy contain declarations of all generic or purely virtual functions, making it possible to use C++ polymorphism to define the functions of the lower classes of the hierarchy to define specific implementations.

There are cases when classes at the top levels of the hierarchy contain only pure virtual functions and no more. Thus, a class containing generic or pure virtual functions does nothing, it is just a link to provide polymorphism. Therefore, it does not make sense to create an instance of such a class, since an object of this class is not needed. This object takes up space in memory but doesn’t do any work. C++ has the ability to restrict the use of “empty” class objects by declaring them abstract.

This way we can define an abstract class. Abstract class is a class that contains at least one pure virtual function. The compiler defines this class in a special way. It will not work to create an object of an abstract class. An attempt to create such an object will be recognized by the compiler as an error. This is logical, why allocate memory for an element that does not define the code that provides the services.

In the most general case, the declaration of an abstract class containing only one pure virtual function has the form.

class ClassName
{
  virtual return_type Func(list_of_parameters) = 0;

  // Other elements of the class
  // ...
}

here

  • ClassName – the name of a class defined as abstract by the compiler based on the presence of a pure virtual function named Func();
  • Func – the name of pure virtual function;
  • return_type – the type that a pure virtual function returns;
  • list_of_parameters – the list of parameters.

Attempting to declare an instance of the ClassName class will result in a compile-time error

ClassName obj; // compilation error, forbidden

However, it is allowed to declare a pointer or a reference to an abstract class

ClassName* p; // declaration of a pointer to the abstract class ClassName

ClassName& ref = obj; // declaring a reference to the abstract class ClassName

In the case of declaring a reference to an abstract class ClassName, this reference must be initialized immediately with the value of the address of an instance of any non-abstract class inherited from ClassName.

 

3. The abstract keyword. Applying to a class. Applying to a function

In C++, there is another form of indicating the fact that a class is abstract – this is the indication of the abstract keyword after the class name. For a class named AbstractClass, the use of the abstract keyword can be as follows

class AbstractClass abstract
{
  // ...
}

In the context above, the abstract keyword specifies:

  • the class is abstract − it will not be possible to create the instance of the abstract class (informative component);
  • class (type) can be used as a base class;
  • functions in the class can be virtual. It is not necessary for a class declared as abstract to contain virtual functions.

Also, in the abstract class (with or without the abstract keyword), pure virtual functions can also be declared with the abstract keyword. For example, for the AbstractClass class, the following declaration takes place

class AbstractClass abstract
{
  // ...
  void SomeVirtualFunc() abstract;
}

In the above code, the declaration of the SomeVirtualFunc() function with the abstract keyword is similar to the declaration

...
void SomeVirtualFunc() = 0;
...

Example.

#include <iostream>
using namespace std;

// The abstract keyword
class A abstract
{
public:
  void Print()
  {
    cout << "A::Print" << endl;
  }

  // Pure virtual function
  virtual void Print2() = 0;

  // A function whose implementation must be in an inherited class 
  virtual void Print3() abstract; // - also as a pure virtual function
};

// Derived class
class B : public A
{
public:
  // Overriding the Print2() function
  void Print2() override
  {
    cout << "B::Print2" << endl;
  }

  // Overriding the Print3() function
  void Print3() override
  {
    cout << "B::Print3" << endl;
  }
};

void main()
{
  //A obj;
  B obj;
  obj.Print();
  obj.Print2();
  obj.Print3();
}

Result

A::Print
B::Print2
B::Print3

 

4. An example demonstrating the features of using the abstract class in a program

The example declares three classes that form a hierarchy (Figure 1):

  • the base abstract class A. This class contains the pure virtual function Func(). The instance of this class cannot be created;
  • a non-abstract class B inherited from class A. In class B, a virtual function Func() is declared, which redefines the function of the same name in the base class A;
  • a non-abstract class C inherited from class B. Class C defines a virtual function named Func(). This function overrides the Func() function of class B.

 

C++. Abstract class. Class hierarchy and virtual functions declaration

Figure 1. Abstract class. Class hierarchy and virtual functions declaration

The program code of the classes corresponding to Figure 1.

#include <iostream>
using namespace std;

// This is an abstract class, an instance (object) of this class cannot be created
class A
{
public:
  // pure virtual function
  virtual void Func() = 0;
};

// A class which is inherited from class A
class B : public A
{
public:
  // override pure virtual function - required (compiler requirement)
  void Func() override
  {
    cout << "B::Func()" << endl;
  }
};

// Class inherited from class B
class C : public B
{
public:
  // override pure virtual function
  void Func() override
  {
    cout << "C::Func()" << endl;
  }
};

int main()
{
  // 1. Attempt to instantiate abstract class A
  // A objA; - it is forbidden, compilation error

  // 2. Attempting to create instances of derived classes B, C
  B objB; // this is possible since class B is not abstract
  C objC; // this is possible since class C is not abstract

  // 3. Declare a pointer to the abstract class A
  A* pA; // It is allowed

  // 3.1. Move pointer pA to instance of derived class C
  pA = &objC;
  pA->Func(); // C::Func() - late binding, polymorphism

  // 3.2. Move the pA pointer to the instance of derived class B
  pA = &objB;
  pA->Func(); // B::Func() - polymorphism in action

  // 4. Declare a reference refA to abstract class A and initialize it
  //    with the value of instance of derived class B
  A& refA = objB;
  refA.Func();   // B::Func() - polymorphism in action
}

The result of the program

C::Func()
B::Func()
B::Func()

 

5. An example of demonstrating the use of an abstract class when implementing inheritance: Figures=>Circle=>CircleColor

The example shows a fragment of the hierarchy for constructing geometric shapes. At the top of the hierarchy lies the abstract class Figures. This class is used to ensure that the virtual function mechanism works correctly. The Figures class defines a geometric figure in general terms. A more specific figure (circle, rectangle, triangle, etc.) can be defined in inherited classes. In accordance with this, the Figures class contains the declaration of the virtual function Area(), which is designed to obtain the area of a figure and must be redefined in inherited classes. Since at this level of the hierarchy (in this class) it is not yet known the area of which figure to calculate (the area of a circle, the area of a rectangle, etc.), the Area() function is of a general nature and is defined as a pure virtual function. Based on the definition of the Area() function as purely virtual, the Figure class will be recognized by the compiler as an abstract class.

Figure 2 shows the class hierarchy for this example.

C++. Abstract classes. Overriding the Area() method in a derived class

Figure 2. Abstract classes. Overriding the Area() method in a derived class

In the future, classes of other geometric figures can be inherited from the Figures class (Triangle – a triangle, Rectangle – a rectangle, etc.). In these classes, the Area() method will also be virtual, but it will have specific area calculation code, unlike the Area() method of the Figures class.

The code that demonstrates the interaction between the abstract class Figures and the non-abstract classes Circle and CircleColor is shown below.

#include <iostream>
using namespace std;

// C++. Abstract class Figure
class Figure
{
  // 1. Internal class fields
private:
  string name; // the name of figure

public:
  // 2. Constructor
  Figure(string _name) :name(_name)
  { }

  // 3. Access methods
  string GetName()
  {
    return name;
  }

  void SetName(string name)
  {
    this->name = name;
  }

  // 4. Pure virtual function Area() - area of a figure
  virtual double Area() = 0;
};

// A class that implements a circle.
// Derived from the class Figure - extends the class Figure
class Circle : public Figure
{
  // 1. Internal hidden class fields
private:
  double x, y; // Coordinates of the circle center
  double r; // circle radius

public:
  // 2. Constructor
  Circle(double x, double y, double r) :Figure("Circle")
  {
    this->x = x;
    this->y = y;
    if (r > 0) this->r = r;
    else
      this->r = 1.0;
  }

  // 3. Access methods
  void GetXYR(double& x, double& y, double& r)
  {
    x = this->x;
    y = this->y;
    r = this->r;
  }

  void SetXYR(double x, double y, double r)
  {
    this->x = x;
    this->y = y;

    // correction of radius if needed
    if (r > 0)
      this->r = r;
    else
      this->r = 1.0;
  }

  // 4. Virtual function Area() - the area of a Circle
  double Area() override
  {
    const double Pi = 3.141592;
    return Pi * r * r;
  }
};

// A class that extends the capabilities of the Circle class by adding a circle color.
class CircleColor : public Circle
{
  // 1. Internal class variables
private:
  unsigned int color = 0; // circle color

public:
  // 2. Constructor
  CircleColor(double x, double y, double r, unsigned int color) :Circle(x, y, r)
  {
    this->color = color;
  }

  // 3. Access methods
  unsigned int GetColor()
  {
    return color;
  }

  void SetColor(unsigned int color)
  {
    this->color = color;
  }
};

void main()
{
  // 1. Class Circle
  // 1.1. Create the instance of Circle class
  Circle cr(1, 3, 2);

  // 1.2. Call the Area() method of the Circle class
  double area = cr.Area();
  cout << "The instance of class Color:" << endl;
  cout << "area = " << area << endl;

  // 1.3. Get the value of the fields of the cr instance
  double x, y, r;
  cr.GetXYR(x, y, r);

  // 1.4. Display the value of the fields on the screen
  cout << "x = " << x << ", y = " << y << ", radius = " << r << endl;

  // 2. The instance of CircleColor class
  // 2.1. Create the instance of CircleColor class
  CircleColor crCol(2, 4, 6, 1);

  // 2.2. Get the value of the fields of the crCol instance
  // 2.2.1. // get the color value from the CircleColor class method
  unsigned int col = crCol.GetColor();

  // 2.2.2. Get x, y, r values from base class method
  crCol.GetXYR(x, y, r);

  // 2.3. Display the received values on the screen
  cout << "The instance of class ColorCircle: " << endl;
  cout << "color = " << col << endl;
  cout << "x = " << x << endl;
  cout << "y = " << y << endl;
  cout << "r = " << r << endl;

  // 2.4. Call the Area() method of the base class
  cout << "area = " << crCol.Area() << endl;
}

Program result

The instance of class Color:
area = 12.5664
x = 1, y = 3, radius = 2
The instance of class ColorCircle:
color = 1
x = 2
y = 4
r = 6
area = 113.097

 

6. Inheritance of abstract classes

Abstract classes at the top of the hierarchy are used primarily for the needs of derived classes. Any class hierarchy can contain from one to several abstract classes. A class derived from abstract can also be abstract. In this case, such a class simply overrides the function of the base abstract class, as in the example below.

// base abstract class
class BaseClass
{
  // Pure virtual function
  virtual return_type Func(list_of_parameters) = 0;
}

// derived abstract class
class DerivedClass : BaseClass
{
  // Overriding a pure virtual function
  virtual return_type Func(list_of_parameters) override = 0;
}

here

  • BaseClass is the base abstract class that defines the pure virtual function Func();
  • DerivedClass is a derived abstract class that overrides the pure virtual function Func();
  • Func() is a pure virtual function;
  • return_type is the type returned by the pure virtual function Func();
  • list_of_parameters – list of parameters of the Func() function.

Also, when an abstract class is inherited, any purely virtual function of that class must be redefined in the derived class. Otherwise, the compiler will generate an error.

For example.

If an abstract class with a purely virtual function is given

class BaseClass
{
  virtual void Func() = 0;
}

then this function must be overridden in the inherited (derived) class. Two possible cases are considered.

Case 1. A virtual function contains some code (function body). Then the example code of the inherited class would be as follows

class DerivedClass : BaseClass
{
  ...

  void Func() override
  {
    // it is necessary to define an override function that contains a block of code
    // ...
  }
}

Case 2. A virtual function is implemented as a pure virtual function (no function body). In this case, this purely virtual function must be overridden in a derived class in the hierarchy.

class DerivedClass : BaseClass
{
  ...

  // continuation of a chain of pure virtual functions
  virtual void Func() override = 0;
}

In the code above, you can omit the virtual keyword.

 


Related topics