Ответ
В C++ полиморфизм времени выполнения реализуется через виртуальные функции и наследование. Механизм основан на таблице виртуальных функций (vtable) и указателе на нее (vptr) в каждом объекте полиморфного класса.
Базовый пример:
#include <iostream>
class Shape {
public:
// Виртуальная функция обеспечивает динамическое связывание
virtual void draw() const {
std::cout << "Drawing a generic shape.n";
}
// Виртуальный деструктор КРИТИЧЕН для корректного удаления объектов через указатель на базовый класс
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
// Ключевое слово 'override' явно указывает на переопределение (проверяется компилятором)
void draw() const override {
std::cout << "Drawing a circle.n";
}
};
class Square : public Shape {
public:
void draw() const override {
std::cout << "Drawing a square.n";
}
};
int main() {
Shape* shapes[] = {new Circle(), new Square()};
for (Shape* s : shapes) {
s->draw(); // Вызовется правильная версия draw() для каждого типа
delete s; // Корректное удаление благодаря виртуальному деструктору
}
return 0;
}
Ключевые элементы:
virtualв базовом классе: помечает функцию как переопределяемую.override(C++11) в производном классе: гарантирует, что функция переопределяет виртуальную функцию базового класса.- Виртуальный деструктор: Обязателен, если возможна работа с объектами через указатель на базовый класс и их удаление.
- Чисто виртуальные функции и абстрактные классы:
virtual void draw() const = 0;делает классShapeабстрактным, его нельзя инстанцировать.
Полиморфизм работает только при вызове через указатель или ссылку на базовый класс.
Ответ 18+ 🔞
А, полиморфизм в C++! Ну это, ёпта, классика жанра, как водка с огурцом. Сидишь такой, пишешь код, а потом бац — и тебе нужно, чтобы одна и та же функция вела себя по-разному в зависимости от того, какой конкретно объект ты ей суёшь. Вот тут-то и начинается магия, а точнее, не магия, а вполне себе конкретный механизм, который компилятор под капотом разворачивает.
Смотри, как это работает, на пальцах. Есть у тебя, допустим, базовый класс Shape — просто какая-то абстрактная «фигура». И ты объявляешь в нём функцию draw() как виртуальную. Это ключевое слово virtual — оно как красная тряпка для компилятора. Оно кричит: «Эй, чувак, эту функцию потомки могут переопределить, так что не связывай вызов жёстко на этапе компиляции, оставь это на потом!».
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing a generic shape.n";
}
virtual ~Shape() = default; // <-- А это, блядь, ОБЯЗАТЕЛЬНО!
};
Зачем виртуальный деструктор? Да чтобы не было пиздеца, когда ты удаляешь объект производного класса через указатель на базовый. Без него у тебя вызовется деструктор только базового класса, а память от наследника так и повиснет — доверия ебать ноль к такой системе. Это одна из самых частых и подлых ошибок.
Ну а дальше плодишь наследников. Круг, квадрат, треугольник — хуй с горы, что угодно.
class Circle : public Shape {
public:
void draw() const override { // Ключевое слово 'override' — твой друг, оно не даст накосячить
std::cout << "Drawing a circle.n";
}
};
И вот тут самый сок. Создаёшь массив указателей на базовый класс Shape, но пихаешь в него на самом деле круги да квадраты.
Shape* shapes[] = {new Circle(), new Square()};
for (Shape* s : shapes) {
s->draw(); // Вот тут-то и происходит волшебство!
delete s;
}
Когда ты вызываешь s->draw(), компилятор не тупо лезет в код класса Shape. Нет! Он смотрит, а на что на самом деле указывает этот указатель s? Ага, на Circle! И тогда он идёт в специальную табличку — таблицу виртуальных функций (vtable), которая есть у каждого такого объекта, и вызывает правильную, переопределённую функцию для круга. Для квадрата — свою. Это и есть полиморфизм времени выполнения. Сам от себя охуеешь, когда впервые поймёшь, как это элегантно устроено.
А если хочешь сделать класс чисто абстрактным, чтобы его вообще нельзя было создавать, объявляешь функцию чисто виртуальной:
virtual void draw() const = 0; // Всё, теперь Shape — это просто идея, контракт для потомков.
И запомни главное: этот фокус работает только когда ты работаешь через указатель или ссылку на базовый класс. Если создашь объект Circle просто как Circle c; и вызовешь c.draw() — никакой магии, вызовется сразу метод круга. Вся эта система с vtable включается только когда есть неопределённость, когда компилятор в момент компиляции не может точно знать, что за объект придёт. Вот такая, блядь, хитрая жопа у полиморфизма в C++. Кажется сложным, но как разберёшься — пизда рулю, очень мощная штука.