Чистые виртуальные функции и абстрактные классы

Последнее обновление: 16.03.2023

Иногда возникает необходимость определить класс, который не предполагает создания конкретных объектов. Например, класс фигуры. В реальности есть конкретные фигуры: квадрат, прямоугольник, треугольник, круг и так далее. Однако абстрактной фигуры самой по себе не существует. В то же время может потребоваться определить для всех фигур какой-то общий класс, который будет содержать общую для всех функциональность. И для описания подобных сущностей используются абстрактные классы.

Абстрактные классы - это классы, которые содержат или наследуют без переопределения хотя бы одну чистую виртуальную функцию. Абстрактный класс определяет интерфейс для переопределения производными классами.

Что такое чистые виртуальные функции (pure virtual functions)? Это функции, которые не имеют определения. Цель подобных функций - просто определить функционал без реализации, а реализацию определят производные классы. Чтобы определить виртуальную функцию как чистую, ее объявление завершается значением "=0". Например, определим абстрактный класс, который представляет геометрическую фигуру:

class Shape
{
public:
	virtual double getSquare() const = 0;     // площадь фигуры
	virtual double getPerimeter() const = 0;  // периметр фигуры
};

Класс Shape является абстрактным, потому что он содержит как минимум одну чистую виртуальную функцию. А в данном случае даже две таких функции - для вычисления площади и периметра фигуры. И ни одна из функций не имеет никакой реализации. В данном случае обе функции являются константными, но это необязательно. Гловно, чтобы любой производный класс от Shape должен будет предоставить для этих функций свою реализацию.

При этом мы не можем создать объект абстрактного класса:

Shape shape{};

Для применения абстрактного класса определим следующую программу:

#include <iostream>
  
class Shape
{
public:
	virtual double getSquare() const = 0;     // площадь фигуры
	virtual double getPerimeter() const = 0;  // периметр фигуры
};
class Rectangle : public Shape  // класс прямоугольника
{
public:
    Rectangle(double w, double h) : width(w), height(h)
    { }
    double getSquare() const override
    {
        return width * height;
    }
    double getPerimeter() const override
    {
        return width * 2 + height * 2;
    }
private:
    double width;   // ширина
    double height;  // высота
};
class Circle : public Shape     // круг
{
public:
    Circle(double r) : radius(r) 
    { }
    double getSquare() const override
    {
        return radius * radius * 3.14;
    }
    double getPerimeter() const override
    {
        return 2 * 3.14 * radius;
    }
private:
    double radius;  // радиус круга
};
 
int main()
{
    Rectangle rect{30, 50};
    Circle circle{30};
     
    std::cout << "Rectangle square: " << rect.getSquare() << std::endl;
    std::cout << "Rectangle perimeter: " << rect.getPerimeter() << std::endl;
    std::cout << "Circle square: " << circle.getSquare() << std::endl;
    std::cout << "Circle perimeter: " << circle.getPerimeter() << std::endl;
}

Здесь определены два класса-наследника от абстрактного класса Shape - Rectangle (прямоугольник) и Circle (круг). При создании классов-наследников все они должны либо определить для чистых виртуальных функций конкретную реализацию, либо повторить объявление чистой виртуальной функции. Во втором случае производные классы также будут абстрактными.

В данном же случае и Circle, и Rectangle являются конкретными классами и реализуют все виртуальные функции.

Консольный вывод программы:

Rectangle square: 1500
Rectangle perimeter: 160
Circle square: 2826
Circle perimeter: 188.4

Стоит отметить, что абстрактный класс может определять и обычные функции и переменные, может иметь несколько конструкторов, но при этом нельзя создавать объекты этого абстрактного класса. Например:

#include <iostream>
  
class Shape
{
public:
    Shape(int x, int y): x{x}, y{y}
    {}
	virtual double getSquare() const = 0;     // площадь фигуры
	virtual double getPerimeter() const = 0;  // периметр фигуры
    void printCoords() const
    {
        std::cout << "X: " << x << "\tY: " << y << std::endl;
    }
private:
    int x;
    int y;
};
class Rectangle : public Shape  // класс прямоугольника
{
public:
    Rectangle(int x, int y, double w, double h) : Shape{x, y}, width(w), height(h)
    { }
    double getSquare() const override
    {
        return width * height;
    }
    double getPerimeter() const override
    {
        return width * 2 + height * 2;
    }
private:
    double width;   // ширина
    double height;  // высота
};
class Circle : public Shape     // круг
{
public:
    Circle(int x, int y, double r) : Shape{x, y}, radius(r) 
    { }
    double getSquare() const override
    {
        return radius * radius * 3.14;
    }
    double getPerimeter() const override
    {
        return 2 * 3.14 * radius;
    }
private:
    double radius;  // радиус круга
};
 
int main()
{
    Rectangle rect{0, 0, 30, 50};
    rect.printCoords();     // X: 0    Y: 0

    Circle circle{10, 20, 30};
    circle.printCoords();   // X: 10   Y: 20
}

В данном случае класс Shape также имеет две переменных, конструктор, который устанавливает их значения, и невиртуальную функцию, которая выводит их значения. В производных классах также необходимо вызвать этот конструктор. Однако объект абстрактного класса с помощью его конструктора мы создать не можем.

Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850