← Назад к вопросам

Что такое шаблон?

1.2 Junior🔥 181 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Шаблоны (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)
✓ Библиотеки общего назначения
✗ Не злоупотребляй, если можно обойтись без них

Что такое шаблон? | PrepBro