Идея динамического полиморфизма
«Полиморфизм (от др. греч. много видов) — возможность существования чего-либо в различных формах»
Идея динамического полиморфизма заключается в том, чтобы иметь возможность обобщенно описать поведение семейства классов. Каждый класс семейства имеет общий набор действий, но может выполнять их по своему — учитывать особенности конкретного представителя семейства.
Приведу простой пример: все подписчики канала относятся к семейству Homo sapiens. Общей чертой этого вида является возможность говорить, но все говорят по-разному! Даже если мы произносим один и тот же текст, будет отличается интонация, голос, диалект, темп... Я делаю акцент на разных способах совершения одного и того же действия. Допустим, мы хотим запрограммировать это разнообразие действий. В языке есть конкретный способ, как это можно сделать легко и понятно. Для упрощенного взаимодействия с такими семействами в язык введен динамический полиморфизм.
В рамках данной темы слово семейство подходит лучше всего. Мы можем сделать иерархию классов, связанных по смыслу. Родительский класс будет обобщать поведение для всех своих наследников. Так получается семейство, где каждый класс имеет некоторое родство с другими классами: общий набор виртуальных методов. Они могут наследоваться от предков и могут быть изменены у потомков. Такой набор представляет из себя общий интерфейс для семейства классов.
Ключевое слово
Реализация виртуального метода может быть изменена у наследников класса:
Каждый представитель семейства — это отдельный класс. Это позволяет им определять разное количество полей с разными типами. В связи с этим, их не получится традиционно положить последовательно друг за другом в какой-то контейнер, например, массив или вектор. Как минимум у них будут разные типы, что недопустимо в языке. Нельзя гарантировать, что размер объектов разных классов будет одинаковый, значит и ходить по памяти с фиксированным шагом нельзя. В этом случае, как правило, объект полиморфного класса создают на куче, а в контейнер сохраняют указатель на эту область памяти. Тип указателя выбирается таким образом, чтобы он был общим предком для всех объектов, которые будут сохраняться в конкретный контейнер. Пример:
Продолжение в комментариях 👇
#cppcore
«Полиморфизм (от др. греч. много видов) — возможность существования чего-либо в различных формах»
Идея динамического полиморфизма заключается в том, чтобы иметь возможность обобщенно описать поведение семейства классов. Каждый класс семейства имеет общий набор действий, но может выполнять их по своему — учитывать особенности конкретного представителя семейства.
Приведу простой пример: все подписчики канала относятся к семейству Homo sapiens. Общей чертой этого вида является возможность говорить, но все говорят по-разному! Даже если мы произносим один и тот же текст, будет отличается интонация, голос, диалект, темп... Я делаю акцент на разных способах совершения одного и того же действия. Допустим, мы хотим запрограммировать это разнообразие действий. В языке есть конкретный способ, как это можно сделать легко и понятно. Для упрощенного взаимодействия с такими семействами в язык введен динамический полиморфизм.
В рамках данной темы слово семейство подходит лучше всего. Мы можем сделать иерархию классов, связанных по смыслу. Родительский класс будет обобщать поведение для всех своих наследников. Так получается семейство, где каждый класс имеет некоторое родство с другими классами: общий набор виртуальных методов. Они могут наследоваться от предков и могут быть изменены у потомков. Такой набор представляет из себя общий интерфейс для семейства классов.
Ключевое слово
virtual
используется для обозначения виртуальных методов:struct ParentКласс, который содержит хотя бы один виртуальный метод, в том числе наследует его от родителя, является полиморфным классом.
{
// Виртуальные методы `whoami` и `can_work`
// могут быть переопределены у наследников
// этого класса
virtual void whoami() {
std::cout << "I'm a human. ";
}
virtual void can_work() {
std::cout << "I can work hard 5 days a week. ";
}
// Невиртуальный метод
void say_hello() {
std::cout << "Hello! ";
}
// Обязательно объявляем
// виртуальный деструктор
virtual ~Parent() {;}
};
Реализация виртуального метода может быть изменена у наследников класса:
struct Child : public ParentВиртуальный метод наследника будет перегружен только тогда, когда сигнатура метода полностью совпадет с сигнатурой у родителя. То есть имя, типы и набор параметров у методов совпадает. Возвращаемый тип тоже должен совпадать, но есть одно исключение: подробнее.
{
// Методы `whoami` наследован
// без переопределения
// Метод `can_work` переопределен
void can_work() override {
std::cout << "I can't work because I'm too young. ";
}
// Метод `say_hello` наследован от родителя
// Метод, недоступный из общего интерфейса
void child_only() {
std::cout << "I can play games hard 7 days a week! ";
}
};
Каждый представитель семейства — это отдельный класс. Это позволяет им определять разное количество полей с разными типами. В связи с этим, их не получится традиционно положить последовательно друг за другом в какой-то контейнер, например, массив или вектор. Как минимум у них будут разные типы, что недопустимо в языке. Нельзя гарантировать, что размер объектов разных классов будет одинаковый, значит и ходить по памяти с фиксированным шагом нельзя. В этом случае, как правило, объект полиморфного класса создают на куче, а в контейнер сохраняют указатель на эту область памяти. Тип указателя выбирается таким образом, чтобы он был общим предком для всех объектов, которые будут сохраняться в конкретный контейнер. Пример:
Parent *humans[2] { nullptr };Указатель
// Parent - общий предок
humans[0] = new Parent();
humans[1] = new Child();
data
имеет тип Parent
. Почему выбран именно этот тип? В данном случае, мы сможем вызывать общие методы не только с классом Parent
, но и со всеми его наследниками. Не обязательно выбирать самый базовый класс, т.к. наследник может предоставлять более широкий интерфейс, содержать больше общих методов для выбранного подсемейства.Продолжение в комментариях 👇
#cppcore