Шаблон класса — это механизм C++, позволяющий создавать параметризованные классы. Параметром шаблона может выступать любой тип или значение допустимого типа. Важно понимать: сам шаблон не является ни типом, ни объектом — это лишь «чертёж» для генерации конкретных классов. Из исходного файла, содержащего только определение шаблона, код не генерируется.
Компилятор создаёт код только тогда, когда из шаблона формируется конкретный класс с заданным набором аргументов. Этот процесс называется инстанцированием.
Синтаксис объявления
Общая форма объявления шаблона класса похожа на шаблон функции, но дополнена ключевым словом class:
template <class T> class имя_класса { };
Вместо class можно использовать typename — оба варианта эквивалентны, но typename считается более современным и явно подчёркивает, что параметр является типом.
Пример: стек на основе шаблона
Рассмотрим реализацию стека в виде шаблона — это позволяет использовать один и тот же код для разных типов данных.
Заголовочный файл vt.h
#ifndef VT_H
#define VT_H
#include <QDebug>
template <typename T>
class Stack
{
private:
T *stack;
int size;
int top;
public:
Stack(int size_new)
{
stack = new T[size_new];
size = size_new;
top = -1;
}
~Stack()
{
delete [] stack;
}
bool push(const T value)
{
if (top == size - 1)
return false;
top++;
stack[top] = value;
return true;
}
bool pull()
{
if (top == -1)
return false;
stack[top] = 0;
top--;
return true;
}
void qDebugStack()
{
for (int i = size - 1; i >= 0; i--)
qDebug() << stack[i];
}
};
#endif // VT_H
Файл main.cpp
#include <QCoreApplication>
#include "vt.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// Благодаря шаблону используем один и тот же класс
// для разных типов данных
Stack<int> StackInt(5); // для значений int
Stack<double> StackDouble(10); // для значений double
// Заполняем стеки
for (int i = 1; i < 6; i++)
StackInt.push(i);
for (int i = 1; i < 11; i++)
StackDouble.push(i);
// Выводим значения
StackInt.qDebugStack(); // 5 4 3 2 1
StackDouble.qDebugStack(); // 10 9 8 7 6 5 4 3 2 1
// Удаляем элементы
StackInt.pull();
StackInt.pull();
StackInt.pull();
StackInt.qDebugStack(); // 0 0 0 2 1
StackDouble.pull();
StackDouble.pull();
StackDouble.pull();
StackDouble.pull();
StackDouble.qDebugStack(); // 0 0 0 0 6 5 4 3 2 1
return a.exec();
}
Вынесение реализации за пределы класса
В приведённом примере реализация методов находится внутри класса. При необходимости её можно вынести наружу — для этого перед объявлением каждой функции нужно добавить определение шаблона.
Вынесем, например, метод pull():
template <class T>
bool Stack<T>::pull()
{
if (top == -1)
return false;
stack[top] = 0;
top--;
return true;
}
В самом классе объявление заменяется на:
bool pull();
Обратите внимание на синтаксис: после Stack<T>:: указывается имя метода, а перед объявлением обязательно стоит template <class T>.
Ключевые особенности шаблонов классов
- Отложенная генерация кода. Компилятор создаёт реализацию только для тех типов, которые фактически использованы в программе. Если шаблон объявлен, но не инстанцирован, кода для него не будет.
- Отсутствие проверки типов до инстанцирования. Ошибки в шаблоне обнаруживаются только при подстановке конкретного типа. Например, если метод использует операцию, не поддерживаемую типом-параметром, компилятор сообщит об этом лишь при создании экземпляра.
- Раздувание кода (code bloat). Каждый инстанцированный тип порождает собственную копию кода класса. При большом числе типов это увеличивает размер бинарного файла.
- Проблема раздельной компиляции. Реализация шаблона обычно размещается в заголовочном файле, поскольку компилятору нужно видеть её при инстанцировании в каждой единице трансляции.
- Параметры-значения. Помимо типов, шаблон может принимать значения — например,
template <typename T, int Size>позволяет задать размер контейнера на этапе компиляции.
Шаблоны классов — мощный инструмент обобщённого программирования, лежащий в основе стандартной библиотеки STL. Они позволяют писать код один раз и применять его к множеству типов, сохраняя типобезопасность и производительность.