Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Шаблоны (Templates) в C++
Шаблон (template) — это мощный механизм параметризации типов и значений в C++, позволяющий писать обобщённый код, который работает с разными типами данных. Это основа обобщённого программирования (Generic Programming).
Основная идея
Вместо того чтобы писать одну функцию/класс для каждого типа, пишешь шаблон с параметром типа:
// БЕЗ шаблонов: дублирование кода
int max_int(int a, int b) { return a > b ? a : b; }
double max_double(double a, double b) { return a > b ? a : b; }
std::string max_string(const std::string& a, const std::string& b) {
return a > b ? a : b;
}
// С шаблонами: один код для всех типов!
template<typename T>
T max_value(T a, T b) {
return a > b ? a : b;
}
Компилятор автоматически генерирует нужные версии:
max_value(5, 10); // Компилятор создаёт max_value<int>
max_value(3.14, 2.71); // Компилятор создаёт max_value<double>
max_value("hello", "world"); // Компилятор создаёт max_value<const char*>
Два типа шаблонов
1. Шаблоны функций (Function Templates)
// Обобщённая функция
template<typename T>
void print(const T& value) {
std::cout << value << std::endl;
}
// Использование
print(42); // T = int
print(3.14); // T = double
print("hello"); // T = const char*
// Явная специализация типа
print<std::string>("world"); // Явно указываем T = std::string
2. Шаблоны классов (Class Templates)
// Обобщённый контейнер
template<typename T>
class Stack {
private:
std::vector<T> data; // T — параметр шаблона
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();
}
};
// Использование
Stack<int> int_stack; // Stack для int
Stack<std::string> str_stack; // Stack для std::string
int_stack.push(42);
str_stack.push("hello");
Параметры шаблонов
Параметр типа (Type Parameter):
template<typename T> // или <class T>
class Container {
T element;
};
Container<int> c1;
Параметр значения (Non-type Parameter):
template<typename T, int Size>
class Array {
private:
T data[Size]; // Size известен во время компиляции
public:
int size() const { return Size; }
};
Array<int, 10> arr1; // Массив из 10 int
Array<double, 5> arr2; // Массив из 5 double
std::cout << arr1.size(); // 10
std::cout << arr2.size(); // 5
Несколько параметров:
template<typename Key, typename Value>
class Pair {
private:
Key key;
Value value;
public:
Pair(const Key& k, const Value& v) : key(k), value(v) {}
const Key& getKey() const { return key; }
const Value& getValue() const { return value; }
};
Pair<std::string, int> person("Alice", 30);
std::cout << person.getKey() << ": " << person.getValue() << std::endl;
Специализация шаблонов
Полная специализация (Explicit Specialization):
// Основной шаблон
template<typename T>
class Printer {
public:
void print(const T& value) {
std::cout << value << std::endl;
}
};
// Специализация для bool
template<>
class Printer<bool> {
public:
void print(bool value) {
std::cout << (value ? "true" : "false") << std::endl;
}
};
Printer<int> p1;
p1.print(42); // Использует основной шаблон
Printer<bool> p2;
p2.print(true); // Использует специализацию
Частичная специализация (Partial Specialization):
// Основной шаблон
template<typename T>
class Container {
void info() { std::cout << "Generic container" << std::endl; }
};
// Специализация для указателей
template<typename T>
class Container<T*> {
void info() { std::cout << "Pointer container" << std::endl; }
};
Container<int> c1;
c1.info(); // Выведет "Generic container"
Container<int*> c2;
c2.info(); // Выведет "Pointer container"
CRTP (Curiously Recurring Template Pattern)
// Базовый класс-шаблон
template<typename Derived>
class Base {
public:
void operation() {
// Приводим 'this' к Derived и вызываем его version
static_cast<Derived*>(this)->implementation();
}
};
// Дочерний класс
class Derived : public Base<Derived> {
public:
void implementation() {
std::cout << "Derived implementation" << std::endl;
}
};
Derived d;
d.operation(); // Вызывает Derived::implementation
Шаблонные методы (Template Methods)
class DataProcessor {
public:
// Шаблонный метод
template<typename Func>
void process(const std::vector<int>& data, Func callback) {
for (int value : data) {
callback(value);
}
}
};
DataProcessor processor;
std::vector<int> numbers = {1, 2, 3, 4, 5};
// С lambda
processor.process(numbers, [](int x) {
std::cout << x * 2 << " ";
});
// С функцией
auto print = [](int x) { std::cout << x << " "; };
processor.process(numbers, print);
Variadic Templates (Шаблоны с переменным числом параметров)
// Шаблон с переменным числом аргументов
template<typename... Args>
void print_all(Args... args) {
// Распаковка параметров
(std::cout << ... << args) << std::endl; // C++17
}
print_all(1, 2.5, "hello", true);
// Выведет: 12.5hellotrue
// Более безопасный способ
template<typename T>
void print_one(const T& value) {
std::cout << value << " ";
}
template<typename First, typename... Rest>
void print_all(const First& first, const Rest&... rest) {
print_one(first);
if constexpr (sizeof...(rest) > 0) {
print_all(rest...);
}
}
print_all(1, 2.5, "hello", true);
// Выведет: 1 2.5 hello 1
Constraints и Concepts (C++20)
// Концепт: требования к типу
template<typename T>
concept Printable = requires(T a) {
std::cout << a; // Тип T должен быть printable
};
// Использование концепта
template<Printable T>
void safe_print(const T& value) {
std::cout << value << std::endl;
}
safe_print(42); // OK, int is printable
safe_print("hello"); // OK, const char* is printable
// safe_print(std::vector<int>()); // ERROR! vector is not printable
Проблемы шаблонов
1. Взрывное увеличение размера кода (Code Bloat):
template<typename T>
T max(T a, T b) { return a > b ? a : b; }
max(1, 2); // max<int>
max(1.0, 2.0); // max<double>
max(1.5f, 2.5f); // max<float>
// Компилятор генерирует три функции, хотя логика одинакова
// Решение: явное приведение типов
int r1 = max(1, 2);
double r2 = max((double)1, (double)2);
float r3 = max((float)1.5, (float)2.5);
2. Ошибки компилятора:
// Если T не поддерживает operator>
template<typename T>
T max(T a, T b) { return a > b ? a : b; }
struct NoComparison {};
max(NoComparison(), NoComparison()); // ОШИБКА компилятора из 10 строк!
3. Медленная компиляция:
// Каждое использование шаблона требует его инстанциирования
// Это может сильно замедлить компиляцию
template<typename T>
class HeavyTemplate { /* ... */ };
HeavyTemplate<int> h1;
HeavyTemplate<double> h2;
HeavyTemplate<float> h3;
// Три раза компилируется одинаковый код
Best Practices
1. Типизируй шаблоны:
// Плохо: неясные требования
template<typename T>
void process(T value) {
value.method(); // Что должен иметь T?
}
// Хорошо: явные требования
template<typename T> requires requires(T t) { t.method(); }
void process(T value) {
value.method();
}
2. Специализируй для оптимизации:
// Основной шаблон (медленный)
template<typename T>
void copy(T* src, T* dst, size_t n) {
for (size_t i = 0; i < n; ++i) {
dst[i] = src[i];
}
}
// Специализация для простых типов (быстрый)
template<>
void copy<int>(int* src, int* dst, size_t n) {
std::memcpy(dst, src, n * sizeof(int));
}
3. Используй static_assert для проверок:
template<typename T>
class Array {
static_assert(std::is_default_constructible_v<T>,
"T must be default constructible");
static_assert(!std::is_const_v<T>,
"T cannot be const");
};
Резюме
Шаблоны — это инструмент для:
✓ Обобщённого программирования (один код для разных типов)
✓ Compile-time вычислений (template metaprogramming)
✓ Типобезопасности (вместо void*)
✓ Производительности (все оптимизации на compile-time)
Недостатки:
✗ Долгая компиляция
✗ Увеличение размера бинарника
✗ Сложные ошибки компилятора
✗ Утечка internal деталей в interface
Когда использовать:
✓ Контейнеры (std::vector, std::map)
✓ Алгоритмы (std::sort, std::find)
✓ Утилиты (std::pair, std::optional)
✓ Библиотеки общего назначения
✗ Не злоупотребляй, если можно обойтись без них