Статический полиморфизм
Избегайте накладных расходов виртуальных вызовов
«Статический полиморфизм» — бесплатный урок C++ Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C++ Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C++ Academy содержит 4 уроков всего.
Два вида полиморфизма
C++ поддерживает два вида полиморфизма.
- Динамический: виртуальные функции разрешаются во время выполнения через виртуальную таблицу
- Статический: шаблоны и CRTP разрешаются на этапе компиляции
Статический полиморфизм позволяет повысить скорость за счёт гибкости.
Цена virtual
Вызов виртуальной функции требует косвенного перехода через виртуальную таблицу. Компилятор обычно не может встроить такой вызов, что блокирует многие оптимизации.
#include <iostream>
struct Shape {
virtual double area() const = 0;
virtual ~Shape() = default;
};
struct Square : Shape {
double s;
Square(double x) : s(x) {}
double area() const override { return s * s; }
};
int main() {
Shape* p = new Square(3);
std::cout << p->area() << "\n";
delete p;
return 0;
}Статическая диспетчеризация с CRTP
С CRTP базовый класс напрямую вызывает метод производного класса через приведение, выполняемое на этапе компиляции, поэтому компилятор может встроить весь код.
#include <iostream>
template <typename T>
struct Shape {
double area() const { return static_cast<const T*>(this)->area(); }
};
struct Square : Shape<Square> {
double s;
Square(double x) : s(x) {}
double area() const { return s * s; }
};
int main() {
Square sq(3);
std::cout << sq.area() << "\n";
return 0;
}Именование перенаправляющего метода
Если базовый и производный классы оба определяют area, дайте перенаправляющему методу базового класса отдельное имя, например compute, чтобы он однозначно вызывал реализацию производного класса.
#include <iostream>
template <typename T>
struct Shape {
double compute() const { return static_cast<const T*>(this)->area(); }
};
struct Circle : Shape<Circle> {
double r;
Circle(double x) : r(x) {}
double area() const { return 3.14159 * r * r; }
};
int main() {
Circle c(2);
std::cout << c.compute() << "\n";
return 0;
}Шаблоны как статический полиморфизм
Обычные шаблоны функций также обеспечивают статический полиморфизм: один и тот же код работает с любым типом, предоставляющим требуемый интерфейс (утиная типизация на этапе компиляции).
#include <iostream>
struct Dog { void speak() const { std::cout << "Woof\n"; } };
struct Cat { void speak() const { std::cout << "Meow\n"; } };
template <typename Animal>
void make_speak(const Animal& a) { a.speak(); }
int main() {
make_speak(Dog{});
make_speak(Cat{});
return 0;
}Когда тип известен
Статический полиморфизм применим только тогда, когда конкретный тип известен во время компиляции. Если необходимо хранить разнородные объекты в одном контейнере и выбирать поведение во время выполнения, виртуальные функции всё ещё необходимы.
Нет гетерогенных контейнеров
Нельзя поместить Shape<Square> и Shape<Circle> в один std::vector в качестве общего базового типа, поскольку это несвязанные типы. В этом заключается основное ограничение статического полиморфизма.
Выигрыш в производительности
Встраивая диспетчеризируемый вызов, компилятор может сворачивать константы и полностью устранять накладные расходы на вызов функции. В плотных циклах над одним известным типом статический полиморфизм может быть значительно быстрее.
#include <iostream>
template <typename T>
struct Op {
int apply(int x) const { return static_cast<const T*>(this)->apply(x); }
};
struct Doubler : Op<Doubler> {
int apply(int x) const { return x * 2; }
};
int main() {
Doubler d;
int sum = 0;
for (int i = 0; i < 5; ++i) sum += d.apply(i);
std::cout << sum << "\n";
return 0;
}Сочетание обоих подходов
Вы можете использовать CRTP для критического участка и тонкую виртуальную оболочку, когда требуется стирание типа. Это обеспечивает скорость там, где она важна, и гибкость там, где она необходима.
Статические проверки интерфейса
Если производный класс забывает реализовать требуемый метод, ошибка появляется во время компиляции при создании экземпляра перенаправляющего метода базового класса, а не во время выполнения.
#include <iostream>
template <typename T>
struct Greeter {
void greet() const { static_cast<const T*>(this)->hello(); }
};
struct English : Greeter<English> {
void hello() const { std::cout << "Hello\n"; }
};
int main() {
English{}.greet();
return 0;
}Выбор подходящего инструмента
Используйте статический полиморфизм, когда:
- Тип известен во время компиляции
- Производительность критически важна
- Вам не нужен общий базовый тип времени выполнения
В противном случае для ясности и гибкости отдавайте предпочтение виртуальным функциям.
Быстрая проверка
Определите основной компромисс статического полиморфизма.
Повторение
Вы сравнили статический и динамический полиморфизм.
- Виртуальные функции = диспетчеризация во время выполнения, таблица виртуальных функций, гетерогенные контейнеры
- CRTP/шаблоны = диспетчеризация во время компиляции, возможность встраивания, более высокая скорость
- Статический подход требует, чтобы тип был известен во время компиляции
- Выбирайте с учётом потребностей в гибкости и производительности
Часто задаваемые вопросы
Урок «Статический полиморфизм» бесплатный?
Да — полный текст урока «Статический полиморфизм» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C++ Academy, подпишись на CoddyKit PRO. Курс C++ Academy содержит 4 уроков всего.
Чему я научусь в уроке «Статический полиморфизм»?
Избегайте накладных расходов виртуальных вызовов Ты практикуешь C++ Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C++ Academy?
Предыдущий опыт не требуется. C++ Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Статический полиморфизм»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C++ Academy?
Да. Каждый урок C++ Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Идиома CRTP
- Статический полиморфизм
- Миксины с CRTP
- Когда использовать CRTP