Полиморфная кухня

5. C++11 и C++14

В пятой части серии рассмотрим возможности наследования и полиморфизма, которые появились в стандарте C++11, а также C++14.

Возьмём иерархию из предыдущей статьи (исходный текст см. там же).

std::is_polymorphic

Стандарт C++11 определяет шаблонную структуру std::is_polymorphic, с помощью которой можно определить, является класс полиморфным или нет.

 std::cout
     << std::boolalpha
     << std::is_polymorphic<Sandwich>::value << std::endl; // true

override

Начиная с C++11, ключевое слово override говорит компилятору: «Я хочу создать в производном классе (производной структуре) замещающую функцию». Это нужно для того, чтобы защититься от случайных ошибок.

Сделаем намеренную ошибку (уберём слово const). Следующий класс не скомпилируется, поскольку компилятор берёт на себя все заботы по проверке корректности. Сигнатуры Sandwich::print() const и Butterbrot::print() не совпадают.

 class Butterbrot : public Sandwich
 {
 public:
     void print() override // Error.
     {
         std::cout << "Butterbrot." << std::endl;
     }
 };

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

В следующей версии сигнатуры совпали, всё нормально, класс скомпилируется.

 class Butterbrot : public Sandwich
 {
 public:
     void print() const override // OK.
     {
         std::cout << "Butterbrot." << std::endl;
     }
 };

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

final

C++11 также ввёл ключевое слово final. Оно говорит, что виртуальная функция не может быть перекрыта в производном классе (производной структуре).

Добавим это слово к функции print() класса ButterbrotWithCaviar.

 class ButterbrotWithCaviar : public Butterbrot
 {
 public:
     void print() const final
     {
         std::cout << "Butterbrot with caviar." << std::endl;
     }
 };

А затем попытаемся создать производный от него и заместить эту функцию.

 class SuperButterbrotWithCaviar : public ButterbrotWithCaviar
 {
 public:
     void print() const override
     {
         std::cout << "Modified butterbrot with caviar." << std::endl;
     }
 };

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

С помощью того же ключевого слова можно запретить наследование от какого-либо класса.

 class HamAndCheeseSandwich final : public HamSandwich, public CheeseSandwich
 {
 public:
     void print() const
     {
         std::cout << "Ham and cheese sandwich." << std::endl;
     }
 };

И уже не получится создать производный от него класс.

 // Error.
 class MySandwich : public Kitchen::HamAndCheeseSandwich
 {
 public:
     void print() const override
     {
         std::cout << "My sandwich." << std::endl;
     }
 };

std::is_final

C++14 вводит шаблонную структуру std::is_final, которая определяет, помечен ли класс словом final.

 std::cout
     << std::boolalpha
     << std::is_final<HamAndCheeseSandwich>::value << '\n' // true
     << std::is_final<Sandwich>::value << std::endl;       // false

И некоторые странности

Ключевые слова override и final являются контекстно-зависимыми. В любом другом месте их можно использовать, к примеру, как название переменной.

И так уж вышло, что слово final применимо к объединениям (unions). Но объединения не наследуются, поэтому единственная вещь, на которую это слово повлияет — это результат std::is_final. В прилагаемом исходном файле есть пример использования.

Заключение

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

Исходные файлы

  1. dynamic_cast-and-rtti.cpp (из предыдущей статьи)
  2. inheritance-and-polymorphism-in-cpp11-and-cpp14.cpp
10 июля 2017 · наследование и полиморфизм · программирование