An abstract class is one which cannot be directly instantiated. Its only purpose is to serve as a common interface to other classes which are derived from it. A class becomes abstract because it has a pure virtual function. This is a virtual function which is declared with = 0 after it.
The = 0 tells the compiler that the function has no body in this class; it is an indication that every concrete derived class is expected to provide an implementation of this function. Once a class contains just a single pure virtual function, the compiler will regard the entire class as abstract, preventing the creation of any objects from this class.
The following is a minimal, complete abstract class example.
#include <iostream>
class Shape
{
public:
virtual void draw() const = 0;
virtual ~Shape()
{
std::cout << "Shape destroyed\n";
}
};
int main()
{
// Shape s; // error: cannot declare variable 's' to be of abstract type 'Shape'
return 0;
}
In this program, the Shape class has a single pure virtual function, draw. Because of this definition, the compiler does not permit any objects of type Shape to be declared. The virtual destructor is still present so that later derived objects can be destroyed through Shape pointers.
A concrete class is one that is derived from an abstract base class and must have its own implementation of all inherited pure virtual functions.
#include <iostream>
class Shape
{
public:
// Make this a pure virtual function
virtual void draw() const = 0;
virtual ~Shape()
{
std::cout << "Shape destroyed\n";
}
};
class Circle : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a circle\n";
}
~Circle()
{
std::cout << "Circle destroyed\n";
}
};
int main()
{
Circle c;
c.draw();
return 0;
}
The Circle class provides a body for the draw function and marks it with the override keyword. Because all of the pure virtual functions of Shape have implementations provided, the Circle class is no longer abstract and allows objects of type Circle to be created. When the draw method is called on an object of type Circle, the appropriate version of the draw function (in Circle) will be invoked.
If any pure virtual function of an abstract base class is not implemented in the derived class, then the derived class is also treated as abstract. The following partial code will cause the compiler to report an error.
class Rectangle : public Shape
{
// no draw() override
};
The error message informs us that Rectangle is abstract because it inherits from Shape and does not implement the pure virtual function draw.
Abstract classes become particularly useful when numerous distinct concrete types must be treated in a similar manner, using a uniform interface. Consider the next example, in which we store several pointers of type Shape to various object types and draw them all without needing to know the exact type of the object at that moment.
#include <iostream>
class Shape
{
public:
virtual void draw() const = 0;
// Run this destructor when the object lifetime ends
virtual ~Shape()
{
std::cout << "Shape destroyed\n";
}
};
class Circle : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a circle\n";
}
~Circle()
{
std::cout << "Circle destroyed\n";
}
};
class Square : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a square\n";
}
~Square()
{
std::cout << "Square destroyed\n";
}
};
void drawAll(Shape* shapes[], int count)
{
for (int i = 0; i < count; ++i)
shapes[i]->draw();
}
int main()
{
Circle c;
Square s;
Shape* list[]{ &c, &s };
drawAll(list, 2);
return 0;
}
The drawAll function receives an array of Shape pointers. In the for loop, each pointer is used to call the draw function. Since draw is a pure virtual function within Shape, and both Circle and Square have provided implementations, the compiler selects the appropriate version of draw to be executed dynamically at runtime. The objects are automatically destroyed when drawAll goes out of scope, and the virtual destructor of Shape ensures that the destructors of the respective concrete types are called first.
An abstract class can have many pure virtual functions. In addition, abstract classes can contain ordinary virtual functions that do have implementations, non virtual functions, and data members. Even a single pure virtual function will cause the entire class to be regarded as abstract. A concrete class is one where all pure virtual functions inherited from one or more abstract base classes have had their implementations provided.
Consequently, the pure virtual function mechanism offers an exact definition of an interface without including a full implementation.
Application code can work only through pointers and references to the abstract base, while actual work is performed by concrete classes that fulfill the interface. The compiler provides the necessary guarantees: object creation for the abstract type is prevented, and every concrete derived class is guaranteed to have the required functions defined.
Exercise: Abstract Classes and Pure Virtual FunctionsStarter code, hints and solutionOpenClose
Practice: Abstract Classes and Pure Virtual Functions
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Add a concrete Square implementing Shape::draw, then call it through Shape&. Explain why Square can be instantiated but Shape cannot.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
class Shape
{
public:
// Make this a pure virtual function
virtual void draw() const = 0;
virtual ~Shape()
{
std::cout << "Shape destroyed\n";
}
};
class Circle : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a circle\n";
}
~Circle()
{
std::cout << "Circle destroyed\n";
}
};
int main()
{
Circle c;
c.draw();
return 0;
}Hints
Shape::draw is pure virtual because its declaration ends in = 0. Shape cannot be instantiated directly, but Circle supplies the required override.
An implementation of the pure virtual draw() const makes the new class concrete. Call it through a Shape& rather than trying to construct Shape itself.
Worked solution
The solution adds a Square implementation of the pure virtual draw function and calls it through Shape&. Square is concrete because it implements draw; Shape remains abstract and cannot be instantiated directly.
#include <iostream>
class Shape
{
public:
// Make this a pure virtual function
virtual void draw() const = 0;
virtual ~Shape()
{
std::cout << "Shape destroyed\n";
}
};
class Circle : public Shape
{
public:
void draw() const override
{
std::cout << "Drawing a circle\n";
}
~Circle()
{
std::cout << "Circle destroyed\n";
}
};
class Square : public Shape
{
public:
void draw() const override
{ std::cout << "Drawing a square\n"; }
};
int main()
{
Circle c;
c.draw();
Square square;
Shape& view{square};
view.draw();
return 0;
}
Example output
Drawing a circle
Drawing a square
Shape destroyed
Circle destroyed
Shape destroyed
0 comments
No comments yet. Be the first.