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
- 1. Pure virtual function. The concept
- 2. Abstract class. General concepts. Features of the declaration and use
- 3. The abstract keyword. Applying to a class. Applying to a function
- 4. An example demonstrating the features of using the abstract class in a program
- 5. An example of demonstrating the use of an abstract class when implementing inheritance: Figures=>Circle=>CircleColor
- 6. Inheritance of abstract classes
- Related topics
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.
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.
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
- Types of relations between classes: is-a, has-a, uses. Examples. Aggregation. Composition
- Polymorphism. Virtual functions. General concepts. Specifiers virtual, override. Examples
⇑