Что такое статический полиморфизм?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Статический полиморфизм в C++
Определение
Статический полиморфизм — это способность писать код, который работает с разными типами на этапе компиляции. Тип определяется компилятором, а не во время выполнения программы.
В отличие от динамического полиморфизма (virtual функции), где тип проверяется в runtime, здесь всё решается на compile-time.
Два основных механизма
1. Шаблоны (Templates) — Template-based polymorphism 2. Перегрузка функций (Overloading) — Ad-hoc polymorphism
1. Шаблоны — самый мощный инструмент
Шаблоны позволяют писать обобщённый код, который компилятор специализирует для каждого типа:
// Обобщённая функция
template <typename T>
void print(T value) {
std::cout << value << std::endl;
}
int main() {
print(42); // Компилятор генерирует print<int>
print(3.14); // Компилятор генерирует print<double>
print("hello"); // Компилятор генерирует print<const char*>
return 0;
}
Компилятор создаёт отдельный код для каждого типа. Это называется instantiation.
Практический пример: обобщённый контейнер
// Template класс
template <typename T>
class Stack {
private:
std::vector<T> data;
public:
void push(const T& value) {
data.push_back(value);
}
T pop() {
T value = data.back();
data.pop_back();
return value;
}
bool empty() const {
return data.empty();
}
};
int main() {
Stack<int> intStack;
intStack.push(1);
intStack.push(2);
std::cout << intStack.pop() << std::endl; // 2
Stack<std::string> strStack;
strStack.push("hello");
strStack.push("world");
std::cout << strStack.pop() << std::endl; // world
// Компилятор создал ДВА разных класса
// Stack<int> и Stack<std::string>
}
2. Перегрузка функций — Ad-hoc полиморфизм
// Одна функция, разные реализации для разных типов
void process(int x) {
std::cout << "Обработка int: " << x << std::endl;
}
void process(double x) {
std::cout << "Обработка double: " << x << std::endl;
}
void process(const std::string& x) {
std::cout << "Обработка string: " << x << std::endl;
}
int main() {
process(42); // Вызывает process(int)
process(3.14); // Вызывает process(double)
process("hello"); // Вызывает process(const std::string&)
}
// Компилятор на этапе компиляции выбирает нужную функцию
Статический vs Динамический полиморфизм
Статический (compile-time):
template <typename T>
void process(T value) {
std::cout << sizeof(T) << std::endl;
}
int main() {
process(42); // sizeof(int) — известно в compile-time
process(3.14); // sizeof(double) — известно в compile-time
// Каждый вызов — разный код в бинаре
}
Динамический (runtime):
class Shape {
public:
virtual void draw() = 0; // virtual — динамическое связывание
};
class Circle : public Shape {
public:
void draw() override { std::cout << "Circle\n"; }
};
class Square : public Shape {
public:
void draw() override { std::cout << "Square\n"; }
};
void render(Shape* shape) {
shape->draw(); // Какой класс? Узнаем только в runtime
}
int main() {
Circle c;
Square s;
render(&c); // Один и тот же код, разные результаты
render(&s);
}
CRTP — Curiously Recurring Template Pattern
Это продвинутая техника статического полиморфизма без virtual функций:
// Базовый класс template
template <typename Derived>
class Base {
public:
void operation() {
static_cast<Derived*>(this)->operationImpl();
}
};
// Производные классы
class DerivedA : public Base<DerivedA> {
public:
void operationImpl() {
std::cout << "DerivedA operation\n";
}
};
class DerivedB : public Base<DerivedB> {
public:
void operationImpl() {
std::cout << "DerivedB operation\n";
}
};
int main() {
DerivedA a;
DerivedB b;
a.operation(); // Вызывает DerivedA::operationImpl
b.operation(); // Вызывает DerivedB::operationImpl
// Полиморфизм БЕЗ virtual!
// Нет вызовов через vtable, всё инлайнится
}
Шаблонная специализация
Различная реализация для разных типов:
// Обобщённая версия
template <typename T>
struct Traits {
static void describe() {
std::cout << "Обобщённый тип\n";
}
};
// Специализация для int
template <>
struct Traits<int> {
static void describe() {
std::cout << "Целое число (int)\n";
}
};
// Специализация для double
template <>
struct Traits<double> {
static void describe() {
std::cout << "Число с плавающей точкой (double)\n";
}
};
int main() {
Traits<int>::describe(); // Целое число (int)
Traits<double>::describe(); // Число с плавающей точкой
Traits<char>::describe(); // Обобщённый тип
}
Преимущества статического полиморфизма
1. Производительность
Код инлайнится и оптимизируется компилятором:
// Статический — компилятор может полностью инлайнить
template <typename T>
T add(T a, T b) { return a + b; }
auto result = add(1, 2); // Может быть заменено просто на 3
// Динамический — никогда не знаем, какой вызов
Shape* s = ...;
s->draw(); // Всегда vtable lookup
2. Отсутствие runtime overhead
Нет виртуальных таблиц, нет вызовов через указатели.
3. Размер типа известен в compile-time
template <typename T>
void printSize() {
std::cout << sizeof(T) << std::endl; // Можно использовать
}
Недостатки
1. Размер бинарного файла (code bloat)
Для каждого типа генерируется отдельный код:
void process(int x) { /* код */ }
void process(double x) { /* тот же код, но для double */ }
void process(float x) { /* тот же код, но для float */ }
// Трёхкратное дублирование кода
2. Сложность компиляции
Шаблоны замедляют компиляцию.
3. Ошибки компиляции сложнее
template <typename T>
void foo(T t) {
t.someMethod(); // Если T не имеет someMethod — ошибка компиляции
}
foo(42); // Ошибка: int не имеет someMethod
// Ошибка будет в определении foo, а не в точке вызова
Когда использовать?
Статический полиморфизм:
- Контейнеры (std::vector<T>, std::queue<T>)
- Алгоритмы, чувствительные к производительности
- Математические библиотеки (Eigen, Armadillo)
Динамический полиморфизм:
- Иерархии классов (фигуры, животные)
- Плагины и расширяемость
- Когда тип известен только в runtime
Заключение
- Статический полиморфизм — выбор типа в compile-time
- Основной механизм: templates и function overloading
- Преимущества: производительность, отсутствие overhead
- Недостатки: код bloat, сложность ошибок, время компиляции
- CRTP — продвинутая техника для virtual-подобного поведения
- Используй статический для максимальной производительности
- Используй динамический для гибкости архитектуры