C++. Абстрактный класс. Чисто виртуальная функция

Абстрактный класс. Чисто виртуальная функция. Ключевое слово abstract. Примеры

Перед изучением данной темы рекомендуется ознакомиться со следующей темой:


Содержание


Поиск на других ресурсах:

1. Чисто виртуальная функция. Понятие

Перед изучением понятия абстрактного класса следует ознакомиться с понятием «чисто виртуальная функция».

Как известно, использование виртуальных функций имеет смысл только в том случае, если классы образуют иерархию наследования. Если нет иерархии классов, то механизм виртуальных функций не работает. Иерархии классов строятся по принципу от общего к конкретному. Это значит, что на верхних уровнях иерархий объявляются классы, определяющие нечто общее. С продвижением к нижним уровням иерархии это общее детализируется (специализируется). Классы нижних уровней содержат конкретные реализации.

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

Если рассмотреть иерархии классов, то очень многие из них содержат виртуальные функции, не имеющие кода или не имеющие тела функции. Иногда классы верхних уровней иерархий содержат только пустые виртуальные функции. В языке C++ виртуальная функция без кода называется чисто виртуальная функция (pure virtual function).

В синтаксисе языка C++ есть возможность объявить чисто виртуальную функцию. Чисто виртуальная функция – это функция, которая:

  • объявлена в классе со спецификатором virtual;
  • объявлена с использованием специальноого синтаксиса C++, который определяет функцию как не имеющую блока фигурных скобок { } содержащего программный код.

В наиболее общем случае объявление чисто виртуальной функции имеет вид:

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

где

  • FuncNamePure() – имя чисто виртуальной функции;
  • return_type – тип, возвращаемый функцией;
  • list_of_parameters – список параметров функции.

Важно понимать, что вышеприведенное объявление чисто виртуальной функции не следует путать с объявлением виртуальной функции, содержащей пустой блок кода

...

// Это не есть чисто виртуальная функция
virtual return_type FuncName(list_of_parameters)
{ }

...

 

2. Абстрактный класс. Общие понятия. Особенности объявления и использования

Если классы образуют иерархию, то классы в верхней части иерархии обычно содержат одну или несколько виртуальных функций. Эти функции используются для согласования взаимодействия других классов, размещаемых на нижних уровнях иерархии. Чаще всего классы верхних уровней иерархии содержат объявления всех общих или чисто виртуальных функций, давая возможность использовать полиморфизм C++ для определения функций низших классов иерархии определяющих конкретные реализации.

Бывают случаи, когда классы верхних уровней иерархии содержат одни только чисто виртуальные функции и больше никаких. Таким образом, класс, содержащий общие или чисто виртуальные функции, ничего не делает, он является лишь связующим звеном для обеспечения полиморфизма. Следовательно, не имеет смысла создавать экземпляр такого класса, поскольку объект этого класса не нужен. Этот объект занимает место в памяти, но не выполняет никакой работы. В языке C++ есть возможность ограничить использование объектов «пустых» классов путем объявления их абстрактными.

Таким образом можно дать определение абстрактного класса. Абстрактный класс – это класс, содержащий хотя бы одну чисто виртуальную функцию. Компилятор этот класс определяет по особенному. Создать объект абстрактного класса не получится. Попытка создания такого объекта будет распознана компилятором как ошибка. Это логично, зачем выделять память под элемент, который не определяет код, предоставляющий услуги.

В наиболее общем случае объявление абстрактного класса, содержащего только одну чисто виртуальную функцию, имеет вид.

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

  // Другие элементы класса
  // ...
}

здесь

  • ClassName – имя класса, определяемого компилятором как абстрактный на основе наличия в нем чисто виртуальной функции с именем Func();
  • Func – имя чисто виртуальной функции;
  • return_type – тип, который возвращает чисто виртуальная функция;
  • list_of_parameters – список параметров.

Попытка объявить экземпляр класса ClassName приведет к ошибке на этапе компиляции

ClassName obj; // ошибка компиляции, запрещено

Однако допускается объявлять указатель или ссылку на абстрактный класс

ClassName* p; // объявление указателя на абстрактный класс ClassName
ClassName& ref = obj; // объявление ссылки на абстрактный класс ClassName

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

 

3. Ключевое слово abstract. Применение к классу. Применение к функции

В C++ существует иная форма указания того факта, что класс есть абстрактный – это указание ключевого слова abstract после имени класса. Для класса с именем AbstractClass использование ключевого слова abstract может быть следующим

class AbstractClass abstract
{
  // ...
}

В вышеприведенном контексте ключевое слово abstract указывает:

  • класс есть абстрактным – создать экземпляр абстрактного класса не удастся (информативная составляющая);
  • класс (тип) может быть использован в качестве базового;
  • функции в классе могут быть виртуальными. Не обязательно класс, объявленный как abstract должен содержать виртуальные функции.

Также в абстрактном классе (с ключевым словом abstract или без него) чисто виртуальные функции могут быть объявлены также с словом abstract. Например, для класса AbstractClass имеет место следующее объявление

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

В вышеприведенном коде объявление функции SomeVirtualFunc() с ключевым словом abstract аналогично объявлению

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

Пример.

#include <iostream>
using namespace std;

// Ключевое слово abstract
class A abstract
{
public:
  void Print()
  {
    cout << "A::Print" << endl;
  }

  // Чисто виртуальная функция
  virtual void Print2() = 0;

  // Функция, реализация которой должна быть в унаследованном классе 
  virtual void Print3() abstract; // - также как чисто виртуальная функция
};

// Производный класс
class B : public A
{
public:
  // Переопределение функции Print2()
  void Print2() override
  {
    cout << "B::Print2" << endl;
  }

  // Переопределение функции Print3()
  void Print3() override
  {
    cout << "B::Print3" << endl;
  }
};

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

Результат

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

 

4. Пример, демонстрирующий особенности использования абстрактного класса в программе

В примере объявляются три класса, образующие иерархию (рисунок 1):

  • базовый абстрактный класс A. Этот класс содержит чисто виртуальную функцию Func(). Экземпляр этого класса создать невозможно;
  • не абстрактный класс B, унаследованный от класса A. В классе B объявляется виртуальная функция Func(), которая переопределяет одноименную функцию базового класса A;
  • не абстрактный класс C, унаследованный от класса B. В классе C определена виртуальная функция с именем Func(). Эта функция переопределяет функцию Func() класса B.

 

C++. Абстрактный класс. Иерархия классов и объявление виртуальных функций

Рисунок 1. Абстрактный класс. Иерархия классов и объявление виртуальных функций

Программный код классов, соответствующих рисунку 1.

#include <iostream>
using namespace std;

// Это есть абстрактный класс, экземпляр (объект) этого класса создать не удастся
class A
{
public:
  // чисто виртуальная функция
  virtual void Func() = 0;
};

// Класс, который унаследован от класса A
class B : public A
{
public:
  // переопределить чисто виртуальную функцию - обязательно (требование компилятора)
  void Func() override
  {
    cout << "B::Func()" << endl;
  }
};

// Класс, унаследованный от класса B
class C : public B
{
public:
  // переопределить чисто виртуальную функцию
  void Func() override
  {
    cout << "C::Func()" << endl;
  }
};

int main()
{
  // 1. Попытка создания экземпляра абстрактного класса A
  // A objA; - запрещено, ошибка компиляции

  // 2. Попытка создания экземпляров производных классов B, C
  B objB; // можно, поскольку класс B не абстрактный
  C objC; // можно, поскольку класс C не абстрактный

  // 3. Объявить указатель на абстрактный класс A
  A* pA; // разрешено

  // 3.1. Переместить указатель pA на экземпляр производного класса C
  pA = &objC;
  pA->Func(); // C::Func() - позднее связывание, полиморфизм

  // 3.2. Переместить указатель pA на экземпляр производного класса B
  pA = &objB;
  pA->Func(); // B::Func() - полиморфизм в действии

  // 4. Объявить ссылку refA на абстрактный класс A и инициализировать его
  //    значением экземпляра производного класса B
  A& refA = objB;
  refA.Func();   // B::Func() - полиморфизм в действии
}

Результат выполнения программы:

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

 

5. Пример демонстрации использования абстрактного класса при реализации наследования: Figures=>Circle=>CircleColor

В примере показан фрагмент иерархии построения геометрических фигур. В вершине иерархии лежит абстрактный класс Figures. Этот класс используется для обеспечения правильной работы механизма виртуальных функций. Класс Figures определяет геометрическую фигуру в общих чертах. Более конкретная фигура (окружность, прямоугольник, треугольник и т.д.) может быть определена в унаследованных классах. В соответствии с этим класс Figures содержит объявление виртуальной функции Area(), которая предназначена для получения площади фигуры и должна быть переопределена в унаследованных классах. Поскольку на этом уровне иерархии (в этом классе) еще неизвестно площадь именно какой фигуры нужно вычислять (площадь круга, площадь прямоугольника и т.п.), то функция Area() носит общий характер и определена как чисто виртуальная функция. На основе определения функции Area() как чисто виртуальной класс Figure будет распознаваться компилятором как абстрактный класс.

На рисунке 2 отображена иеархия классов данного примера.

C++. Абстрактные классы. Переопределение метода Area() в унаследованном классе

Рисунок 2. Абстрактные классы. Переопределение метода Area() в унаследованном классе

В будущем, из класса Figures можно наследовать классы других геометрических фигур (Triangle – треугольник, Rectangle – прямоугольник и т.п.). В этих классах метод Area() будет также виртуальным, но он будет иметь конкретный код вычисления площади в отличие от метода Area() класса Figures.

Программный код, демонстрирующий взаимодействие между абстрактным классом Figures и неабстрактными классами Circle и CircleColor, приведен ниже.

#include <iostream>
using namespace std;

// C++. Абстрактный класс Figure
class Figure
{
  // 1. Внутренние поля класса
private:
  string name; // название фигуры

public:
  // 2. Конструктор
  Figure(string _name) :name(_name)
  {
  }

  // 3. Методы доступа (геттеры, сеттеры)
  string GetName()
  {
    return name;
  }

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

  // 4. Чисто виртуальная функция Area() - площадь фигуры
  virtual double Area() = 0;
};

// Класс, реализующий окружность.
// Является производным от класса Figure – расширяет класс Figure
class Circle : public Figure
{
  // 1. Внутренние скрытые поля класса
private:
  double x, y; // Координаты центра окружности
  double r; // радиус окружности

public:
  // 2. Конструктор класса
  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. Методы доступа
  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;

    // откорректировать радиус если нужно
    if (r > 0)
      this->r = r;
    else
      this->r = 1.0;
  }

  // 4. Виртуальная функция Area() - площадь фигуры
  double Area() override
  {
    const double Pi = 3.141592;
    return Pi * r * r;
  }
};

// Класс, расширяющий возможности класса Circle – добавляет цвет окружности.
class CircleColor : public Circle
{
  // 1. Внутренние переменные класса
private:
  unsigned int color = 0; // цвет окружности

public:
  // 2. Конструктор
  CircleColor(double x, double y, double r, unsigned int color) :Circle(x, y, r)
  {
    this->color = color;
  }

  // 3. Методы доступа
  unsigned int GetColor()
  {
    return color;
  }

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

void main()
{
  // 1. Класс Circle
  // 1.1. Создать экзземпляр класса Circle
  Circle cr(1, 3, 2);

  // 1.2. Вызвать метод Area() класса Circle
  double area = cr.Area();
  cout << "The instance of class Color:" << endl;
  cout << "area = " << area << endl;

  // 1.3. Взять значение полей экземпляра cr
  double x, y, r;
  cr.GetXYR(x, y, r);

  // 1.4. Вывести значение полей на экран
  cout << "x = " << x << ", y = " << y << ", radius = " << r << endl;

  // 2. Экземпляр класса CircleColor
  // 2.1. Создать экземпляр класса CircleColor
  CircleColor crCol(2, 4, 6, 1);

  // 2.2. Получить значение полей экземпляра crCol
  // 2.2.1. // взять значение цвета из метода класса CircleColor
  unsigned int col = crCol.GetColor();

  // 2.2.2. Взять значения x, y, r из метода базового класса
  crCol.GetXYR(x, y, r);

  // 2.3. Вывести полученные значения на экран
  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. Вызвать метод Area() базового класса
  cout << "area = " << crCol.Area() << endl;
}

Результат выполнения программы

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. Наследование абстрактных классов

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

// базовый абстрактный класс
class BaseClass
{
  // Чисто виртуальная функция
  virtual return_type Func(list_of_parameters) = 0;
}

// производный абстрактный класс
class DerivedClass : BaseClass
{
  // Переопределение чисто виртуальной функции
  virtual return_type Func(list_of_parameters) override = 0;
}

здесь

  • BaseClass – базовый абстрактный класс, определяющий чисто виртуальную функцию Func();
  • DerivedClass – производный абстрактный класс, который переопределяет чисто виртуальную функцию Func();
  • Func() – чисто виртуальная функция;
  • return_type – тип, возвращаемый чисто виртуальной функцией Func();
  • list_of_parameters – список параметров функции Func().

Кроме того, при наследовании абстрактного класса любая чисто виртуальная функция этого класса должна быть переопределена в производном классе. В противном случае компилятор выдаст ошибку.

Например.

Если задан абстрактный класс с чисто виртуальной функцией

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

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

Случай 1. Виртуальная функция содержит некоторый код (тело функции). Тогда примерный код унаследованного класса будет следующим

class DerivedClass : BaseClass
{
  ...

  void Func() override
  {
    // обязательно нужно определять override-функцию,
    // которая содержит блок кода
    // ...
  }
}

Случай 2. Виртуальная функция реализована как чистая виртуальная функция (без тела функции). В этом случае эта чисто виртуальная функция должна быть переопределена в производном классе в иерархии.

class DerivedClass : BaseClass
{
  ...

  // продолжение цепочки чисто виртуальных функций
  virtual void Func() override = 0;
}

В вышеприведенном коде можно опустить ключевое слово virtual.

 


Связанные темы