Введение: что такое generics в C++ и зачем они нужны
В мире современного программирования переиспользование кода — один из ключевых принципов. Если вы пишете функцию суммирования для int, а затем для double и float, то рано или поздно захотите избежать дублирования. Именно здесь на помощь приходят generics (обобщённое программирование). В C++ эта концепция реализована через шаблоны (templates).
Generics позволяют писать код, который работает с любым типом данных, не жертвуя производительностью (в отличие от виртуальных функций или void*). Компилятор генерирует отдельную версию функции или класса для каждого конкретного типа, что даёт максимальную скорость выполнения.
В этой статье мы разберём, зачем нужны generics в C++, как их правильно объявлять и использовать на практике, а также рассмотрим типичные ошибки и продвинутые техники.
Основы синтаксиса: как объявить и использовать шаблон
Шаблон в C++ объявляется с помощью ключевого слова template и угловых скобок <>, внутри которых указывается параметр-тип (обычно typename или class).
Функция-шаблон
Простейший пример — функция, возвращающая максимум из двух значений:
template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
// Использование:
int main() {
std::cout << max(3, 7) << std::endl; // 7
std::cout << max(3.14, 2.71) << std::endl; // 3.14
std::cout << max('a', 'z') << std::endl; // z
return 0;
}
Обратите внимание: компилятор сам выводит тип T на основе переданных аргументов. Если нужно явно указать тип, можно написать max<double>(3.14, 2.71).
Класс-шаблон
Шаблоны работают и с классами. Классический пример — простой контейнер «пара»:
template <typename T1, typename T2>
class Pair {
private:
T1 first;
T2 second;
public:
Pair(T1 f, T2 s) : first(f), second(s) {}
T1 getFirst() const { return first; }
T2 getSecond() const { return second; }
};
// Использование:
Pair<int, std::string> student(1, "Alice");
std::cout << student.getFirst() << " " << student.getSecond();
Здесь T1 и T2 — параметры шаблона, которые могут быть любыми типами.
Практические примеры: от простых контейнеров до алгоритмов
Рассмотрим несколько реальных сценариев, где generics незаменимы.
Универсальная сортировка
Напишем шаблонную функцию сортировки массива любого типа, поддерживающего оператор <:
template <typename T>
void bubbleSort(T arr[], int n) {
for (int i = 0; i < n - 1; ++i)
for (int j = 0; j < n - i - 1; ++j)
if (arr[j] > arr[j + 1])
std::swap(arr[j], arr[j + 1]);
}
// Пример:
int intArr[] = {5, 2, 8, 1};
bubbleSort(intArr, 4); // Работает с int
double dblArr[] = {3.14, 2.71, 1.41};
bubbleSort(dblArr, 3); // Работает с double
Стек с произвольным типом данных
Реализуем простой стек на основе шаблона:
template <typename T>
class Stack {
private:
std::vector<T> elements;
public:
void push(const T& value) { elements.push_back(value); }
void pop() { if (!elements.empty()) elements.pop_back(); }
T top() const { return elements.back(); }
bool empty() const { return elements.empty(); }
};
// Использование:
Stack<int> intStack;
intStack.push(10);
intStack.push(20);
std::cout << intStack.top(); // 20
Алгоритм «найти и заменить»
Шаблонная функция, заменяющая все вхождения одного значения на другое в контейнере:
template <typename Container, typename T>
void replaceAll(Container& c, const T& oldVal, const T& newVal) {
for (auto& elem : c) {
if (elem == oldVal) elem = newVal;
}
}
// Пример с vector<string>:
std::vector<std::string> words = {"cat", "dog", "cat"};
replaceAll(words, std::string("cat"), std::string("mouse"));
Продвинутые техники: специализация и нетиповые параметры
Шаблоны C++ позволяют не только работать с любыми типами, но и настраивать поведение для конкретных случаев.
Специализация шаблона
Иногда нужно, чтобы для определённого типа работала особая версия кода. Например, для типа bool можно упаковать значения в биты:
// Общий шаблон
template <typename T>
class Storage {
T data;
public:
Storage(T d) : data(d) {}
void print() { std::cout << data << std::endl; }
};
// Полная специализация для bool
template <>
class Storage<bool> {
bool data;
public:
Storage(bool d) : data(d) {}
void print() { std::cout << (data ? "true" : "false") << std::endl; }
};
Также существует частичная специализация, когда конкретизируется только часть параметров (например, для указателей).
Нетиповые параметры шаблона
Параметром шаблона может быть не только тип, но и значение (целое число, указатель, перечисление). Это часто используется для статических массивов:
template <typename T, int Size>
class StaticArray {
private:
T arr[Size];
public:
int getSize() const { return Size; }
T&