Абстрактные классы (C++)
Абстрактные классы используются в качестве обобщенных концепций, на основе которых можно создавать более конкретные производные классы. Невозможно создать объект абстрактного типа класса. Однако можно использовать указатели и ссылки на абстрактные типы классов.
Вы создаете абстрактный класс, объявляя по крайней мере одну чистую виртуальную функцию-член. Это виртуальная функция, объявленная с помощью синтаксиса чистого описателя ( = 0 ). Классы, производные от абстрактного класса, должны реализовывать чисто виртуальную функцию; в противном случае они также будут абстрактными.
Рассмотрим пример, представленный в функциях Virtual. Класс Account создан для того, чтобы предоставлять общие функции, но объекты типа Account имеют слишком общий характер для практического применения. Это означает Account , что это хороший кандидат для абстрактного класса:
// deriv_AbstractClasses.cpp // compile with: /LD class Account < public: Account( double d ); // Constructor. virtual double GetBalance(); // Obtain balance. virtual void PrintBalance() = 0; // Pure virtual function. private: double _balance; >;
Единственное различие между этим и предыдущим объявлениями состоит в том, что функция PrintBalance объявлена со спецификатором чисто виртуальной функции pure ( = 0 ).
Ограничения на использование абстрактных классов
Абстрактные классы нельзя использовать для:
- переменных и данных членов;
- типов аргументов;
- типов возвращаемых функциями значений;
- типов явных преобразований.
Если конструктор абстрактного класса вызывает чистую виртуальную функцию напрямую или косвенно, результат не определен. Однако конструкторы и деструкторы абстрактных классов могут вызывать другие функции-члены.
Определенные чистые виртуальные функции
Чистые виртуальные функции в абстрактных классах можно определить или реализовать. Такие функции можно вызывать только с помощью полного синтаксиса:
Определенные чистые виртуальные функции полезны при разработке иерархий классов, базовые классы которых включают чистые виртуальные деструкторы. Это связано с тем, что деструкторы базового класса всегда вызываются во время уничтожения объектов. Рассмотрим следующий пример:
// deriv_RestrictionsOnUsingAbstractClasses.cpp // Declare an abstract base class with a pure virtual destructor. // It's the simplest possible abstract class. class base < public: base() <>// To define the virtual destructor outside the class: virtual ~base() = 0; // Microsoft-specific extension to define it inline: // virtual ~base() = 0 <>; >; base::~base() <> // required if not using Microsoft extension class derived : public base < public: derived() <>~derived() <> >; int main() < derived aDerived; // destructor called when it goes out of scope >
В примере показано, как расширение компилятора Майкрософт позволяет добавлять встроенное определение в чистую виртуальную ~base() . Его можно также определить за пределами класса с помощью base::~base() <> .
Когда объект aDerived выходит из область, вызывается деструктор класса derived . Компилятор создает код для неявного вызова деструктора класса base после derived деструктора. Пустая реализация для чистой виртуальной функции ~base гарантирует, что для функции существует хотя бы какая-то реализация. Без него компоновщик создает неразрешенную ошибку внешнего символа для неявного вызова.
В предыдущем примере чистая виртуальная функция base::~base вызывается неявно из derived::~derived . Кроме того, можно явно вызывать чистые виртуальные функции с помощью полного имени функции-члена. Такие функции должны иметь реализацию, или вызов приводит к ошибке во время связи.
Что такое абстрактный класс в С++
Абстрактный класс в C++ — это класс, в котором объявлена хотя бы одна чисто виртуальная функция.
Описание
Абстрактный класс используют, когда необходимо создать семейство классов (много разновидностей монстров в игре), при этом было бы лучше вынести общую реализацию и поведение в отдельный класс. При такой тактике переопределить/дописать придется только специфичные для каждого класса методы (у каждого монстра своя анимация удара/перемещения) и/или расширить функциональность класса.
Но возникает возможность создать абстрактный класс, что противоречит архитектуре: как «выделанная общая часть» может быть полноценным классом? Ответ: никак, надо запретить создавать подобный класс. Для этого указывают один из методов как чистый виртуальный ( pure virtual method ), пример для С++:
virtual void f() = 0;
тем самым заставляя классы наследники определить реализацию для данного метода. Понятное дело, что такое «ограничение» вызывает зависимость и бьет по гибкости, поэтому в 90% случаев таким методом делают деструктор, так как кто-кто, а деструктор в классах, где есть наследование и виртуальные методы нужен всегда. Но не стоит забывать, что при вызове деструктора класс вызывает все деструкторы своих родителей, и поэтому мы объязаны написать реализацию деструктора, в случае определения его как чисто виртуального в абстрактном классе:
virtual ~IUnit() = 0
Класс или его наследник перестаёт быть для компилятора абстрактным и экземпляр такого класса может быть создан, как только определена реализация для каждого pure virtual method.
Отличие от интерфейса
Так как понятия «интерфейс» и «абстрактный класс» путают, приведу их отличия:
- Каждый интерфейс является абстрактным классом, не каждый абстрактный класс — интерфейс (прим. #1).
- Интерфейс содержит только public секцию, абстрактный класс не имеет ограничений.
- Интерфейс содержит только pure virtual methods, абстрактный класс может содержать и поля, и обычные методы в добавок.
- Интерфейс реализуют, абстрактный класс наследуют (для С++ прим. #1)
Примечение #1: так как в С++ нету понятия интерфейс на уровне языка, то программисты симулируют его поведение через абстрактный класс, наследуя его.
Пример
class AbstractUnit < private: int m_hp; double m_speed; protected: virtual void animateMoveToPoint( const Point2D& moveToPoint ) = 0; public: virtual ~AbstractUnit() = 0 <>void setHP(const int hp); int getHP() const; void setSpeed( const double speed ); double getSpeed() const; void runTo( const Point2D& moveToPoint ); >; class Archer : public AbstractUnit < public: Archer() < setHP( 155 ); setSpeed( 400 ); >~Archer() < >void animateMoveToPoint( const Point2D& moveToPoint ) override < // play particle // play sound // run skeleton animation >>;
Интересный, как по мне, способ использовать абстрактный класс в связке с этим паттерном Template Method Design Pattern.
Как создать объект абстрактного класса c
Иногда возникает необходимость определить класс, который не предполагает создания конкретных объектов. Например, класс фигуры. В реальности есть конкретные фигуры: квадрат, прямоугольник, треугольник, круг и так далее. Однако абстрактной фигуры самой по себе не существует. В то же время может потребоваться определить для всех фигур какой-то общий класс, который будет содержать общую для всех функциональность. И для описания подобных сущностей используются абстрактные классы.
Абстрактные классы — это классы, которые содержат или наследуют без переопределения хотя бы одну чистую виртуальную функцию. Абстрактный класс определяет интерфейс для переопределения производными классами.
Что такое чистые виртуальные функции (pure virtual functions)? Это функции, которые не имеют определения. Цель подобных функций — просто определить функционал без реализации, а реализацию определят производные классы. Чтобы определить виртуальную функцию как чистую, ее объявление завершается значением «=0». Например, определим абстрактный класс, который представляет геометрическую фигуру:
class Shape < public: virtual double getSquare() const = 0; // площадь фигуры virtual double getPerimeter() const = 0; // периметр фигуры >;
Класс Shape является абстрактным, потому что он содержит как минимум одну чистую виртуальную функцию. А в данном случае даже две таких функции — для вычисления площади и периметра фигуры. И ни одна из функций не имеет никакой реализации. В данном случае обе функции являются константными, но это необязательно. Гловно, чтобы любой производный класс от Shape должен будет предоставить для этих функций свою реализацию.
При этом мы не можем создать объект абстрактного класса:
Shape shape<>;
Для применения абстрактного класса определим следующую программу:
#include 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; Circle circle; std::cout
Здесь определены два класса-наследника от абстрактного класса Shape — Rectangle (прямоугольник) и Circle (круг). При создании классов-наследников все они должны либо определить для чистых виртуальных функций конкретную реализацию, либо повторить объявление чистой виртуальной функции. Во втором случае производные классы также будут абстрактными.
В данном же случае и Circle, и Rectangle являются конкретными классами и реализуют все виртуальные функции.
Консольный вывод программы:
Rectangle square: 1500 Rectangle perimeter: 160 Circle square: 2826 Circle perimeter: 188.4
Стоит отметить, что абстрактный класс может определять и обычные функции и переменные, может иметь несколько конструкторов, но при этом нельзя создавать объекты этого абстрактного класса. Например:
#include class Shape < public: Shape(int x, int y): x, y <> virtual double getSquare() const = 0; // площадь фигуры virtual double getPerimeter() const = 0; // периметр фигуры void printCoords() const < std::cout private: int x; int y; >; class Rectangle : public Shape // класс прямоугольника < public: Rectangle(int x, int y, double w, double h) : Shape, 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, 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; rect.printCoords(); // X: 0 Y: 0 Circle circle; circle.printCoords(); // X: 10 Y: 20 >
В данном случае класс Shape также имеет две переменных, конструктор, который устанавливает их значения, и невиртуальную функцию, которая выводит их значения. В производных классах также необходимо вызвать этот конструктор. Однако объект абстрактного класса с помощью его конструктора мы создать не можем.
abstract (Справочник по C#)
Модификатор abstract указывает, что изменяемый элемент имеет отсутствующую или неполную реализацию. Модификатор abstract можно использовать с классами, методами, свойствами, индексаторами и событиями. Используйте модификатор abstract в объявлении класса, чтобы указать, что класс предназначен только для использования в качестве базового класса для других классов и не должен быть создан сам по себе. Элементы с пометкой abstract должны быть реализованы не абстрактными классами, производными от абстрактного класса.
Пример 1
В этом примере класс Square должен обеспечивать реализацию GetArea , поскольку является производным от класса Shape :
abstract class Shape < public abstract int GetArea(); >class Square : Shape < private int _side; public Square(int n) =>_side = n; // GetArea method is required to avoid a compile-time error. public override int GetArea() => _side * _side; static void Main() < var sq = new Square(12); Console.WriteLine($"Area of the square = "); > > // Output: Area of the square = 144
Абстрактные классы предоставляют следующие возможности:
- Создавать экземпляры абстрактного класса нельзя.
- Абстрактный класс может содержать абстрактные методы и методы доступа.
- Изменить абстрактный класс с модификатором sealed нельзя, так как два этих модификатора имеют взаимоисключающие значения. Модификатор sealed запрещает наследование класса, в то время как модификатор abstract указывает, что класс обязан иметь производные классы.
- Неабстрактный класс, производный от абстрактного класса, должен включать фактические реализации всех наследуемых абстрактных методов и методов доступа.
Модификатор abstract в объявлении метода или свойства позволяет указать, что этот метод или свойство не содержат реализации.
Абстрактные методы предоставляют следующие возможности:
- Абстрактный метод неявно представляет собой виртуальный метод.
- Объявления абстрактных методов допускаются только в абстрактных классах.
- Поскольку объявление абстрактного метода не предоставляет фактической реализации, тело метода отсутствует, а объявление метода заканчивается точкой с запятой, и фигурных скобок (< >) после подписи нет. Пример:
public abstract void MyMethod();
Действие абстрактных свойств аналогично абстрактным методам, за исключением отличий в синтаксисе объявлений и вызовов.
- Использование модификатора abstract в статическом свойстве является недопустимым.
- Абстрактное наследуемое свойство можно переопределить в производном классе, включив объявление свойства, которое использует модификатор override.
Дополнительные сведения об абстрактных классах см. в разделе Абстрактные и запечатанные классы и члены классов.
Абстрактный класс должен предоставлять реализацию для всех членов интерфейса.
Абстрактный класс, реализующий интерфейс, может сопоставлять методы интерфейса с абстрактными методами. Пример:
interface I < void M(); >abstract class C : I
Пример 2
В следующем примере класс DerivedClass является производным от абстрактного класса BaseClass . Абстрактный класс содержит абстрактный метод, AbstractMethod , и два абстрактных свойства, X и Y .
// Abstract class abstract class BaseClass < protected int _x = 100; protected int _y = 150; // Abstract method public abstract void AbstractMethod(); // Abstract properties public abstract int X < get; >public abstract int Y < get; >> class DerivedClass : BaseClass < public override void AbstractMethod() < _x++; _y++; >public override int X // overriding property < get < return _x + 10; >> public override int Y // overriding property < get < return _y + 10; >> static void Main() < var o = new DerivedClass(); o.AbstractMethod(); Console.WriteLine($"x = , y = "); > > // Output: x = 111, y = 161
В предыдущем примере при попытке создать экземпляр абстрактного класса с помощью оператора вида:
BaseClass bc = new BaseClass(); // Error
Выдается сообщение об ошибке, указывающее, что компилятор не может создать экземпляр абстрактного класса BaseClass.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
- Справочник по C#
- Руководство по программированию на C#
- Модификаторы
- virtual
- override
- Ключевые слова в C#
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.