Заметки по языку с и с++

Шаблоны классов в C++: параметризация типов и практический пример

обновлено 669 просмотров

Шаблон класса — это механизм 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. Они позволяют писать код один раз и применять его к множеству типов, сохраняя типобезопасность и производительность.