Несколько слов об абстрактных базовых классах в C++

Статья предназначена в первую очередь начинающим разработчикам C++, пытающимся разобраться в чужом исходном коде. Ну и для общего развития всем изучающим C++.

Если в исходном тексте есть класс типа такого:

class A {
    virtual void foo() = 0;
};

То есть, не просто virtual, но еще и =0, то такой метод называется не просто “виртуальным”, а “чистым виртуальным” (pure virtual). Что такое pure virtual можете погуглить для дополнительной информации. Тогда и только тогда этот метод не может иметь реализации. Если =0 нет, то реализация метода обязан быть. Хотя бы пустая:

void A::foo() 
{
} 

Класс, который обладает хотя бы одним чисто виртуальным методом называется “Абстрактным базовым классом” (это тоже термин – гуглите). Далее в этой статье – АБК.

Язык С++ этого не регламентирует, но, как правило, програмисты создают абстрактные базовые классы, которые содержат только чисто виртуальные методы. То есть, не обладают ни одним не чисто виртуальным методом, и ни одним членом данным. В других языках программирования (Java, C#) такой класс называется “Интерфейс” (или “протокол” в Objective C). Вся реализация для таких методов делается только в классах, наследованных от АБК. Которые в других язык программирования называют реализацией (имплементацией) интерфейса.

Кстати, в С++ иногда применяют терминологию других ОО языков программирования. Это нормально. Если в наследованном классе есть хотя бы один чисто виртуальный метод из АБК, который не имеет в нем реализации, то такой класс все еще остается абстрактным базовым. В Java и C# такое невозможно. То есть, если заявлено, что некоторый класс реализует какой-то интерфейс, значит он обязан реализовать все методы интерфейса.  

Есть несколько особенностей с АБК:

1) нельзя создать экземпляр АБК. За этим следит компилятор. То есть вот так:

A a; 

сделать нельзя. Зато можно вот так:

A* a;
B b; 
A& a = b;

2) это касается не только АБК, но и вообще наследования. Нельзя вызывать виртуальный метод в конструкторе. Причем если просто при наследовании в таком случае реально вызовется метод данного класса, вместо базового, как можно было бы ожидать. То в случае, если с АБК, программа рухнет в процессе выполнения с ошибкой:

pure virtual method called

Ну или как-то так.

Виртуальные функции это инструмент для реализации полиморфизма в С++. Вот так  это можно явно увидеть:

void bar(A& a) 
{
  a.foo();
}
...
B b;
C c; // B и C наследники A
bar(b); // в bar вызовется метод foo из B
bar(c); // в bar вызовется метод foo из C

причем не важно является A АБК или просто базовый класс с виртуальным методом foo. Разница между АБК и просто базовым классом с виртуалным методом, то что в случае просто базового класса можно сделать так:

A a;
bar(a);

а в случае АБК – нет, так как, как я уже сказал: нельзя создать экземпляр класса A.

Иногда требуется, что бы один и тот же класс реализовывал несколько интерфейсов.
Построим немного отвлеченную модель.
Пусть есть кошка.
С точки зрения бабушки, у которой эта кошка живет, кошка:

  • мурлычит
  • играет с клубками
  • ест
  • гадит

С точки зрения мышки:

  • у нее страшные когти
  • у нее страшные зубы
  • она меня сожрет

С точки зрения ветеринара:

  • мышцы
  • скелет
  • внутренние органы

Это три разных абстракции (интерфейса) одного и тоже класса кошка. Так как мы можем предположить, что для мышки страшным может являться не только кошка но и собака, логично интерфейс кошки с точки зрения мышки, например, “мышкодав” вынести в отдельный АБК (интерфейс). Говоря по другому “определить абстракцию”.

Аналогично можно поступить и для прочих абстракций. То есть, мы описываем абстракции, для разных точек зрения групп классов. Эти абстракции мы записываем на используемом нами языке программирования в виде интерфейсов или АБК. А затем, реализуя класс наследуемся от нужных нам АБК (реализуем нужные нам интерфейсы) в нашем конретном классе. Например, класс кошка.

 

Leave a Reply