Generics в C++: зачем нужны и как использовать на практике

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: что такое 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&                

Блоги

Рекомендаций по книгам