Урок №177. Явная специализация шаблона функции

  Юрий Ворон  | 

    | 

  Обновлено 11 Ноя 2018  | 

 595

При создании экземпляра шаблона функции для определенного типа данных компилятор копирует шаблон функции и заменяет параметр типа шаблона функции на фактический (передаваемый) тип данных. Это означает, что все экземпляры функции имеют одну реализацию, но разные типы данных. Хотя в большинстве случаев это именно то, что требуется, иногда нам может понадобиться, чтобы реализация шаблона функции для одного типа данных отличалась от реализации шаблона функции для другого типа данных.

Специализация шаблонов именно для этого и предназначена.

Рассмотрим очень простой шаблон класса:

Код выше работает со многими типами данных:

Результат:

7
8.4



Теперь, предположим, нам нужно, чтобы значения типа double (только типа double) выводились в экспоненциальной записи. Для этого мы можем использовать специализацию шаблона функции (или «полную/явную специализацию шаблона функции») для создания отдельной версии функции print() для вывода значений типа double.

Всё просто: записываем экземпляр шаблона функции (если функция является методом класса, то делаем это за пределами класса), указывая нужный нам тип данных. Например, вот специальный шаблон функции print() для значений типа double:

Когда компилятору нужно будет создать экземпляр Repository<double>::print(), он увидит, что мы уже явно определили эту функцию, и поэтому он будет использовать именно этот экземпляр, а не копировать версию, общую для всех типов данных, шаблона функции print().

Часть template <> сообщает компилятору, что это шаблон функции, но без параметров (так как в этом случае мы явно указываем нужный нам тип данных).

Результат выполнения кода выше:

7
8.400000e+00

Другой пример

Рассмотрим еще один случай, где специализация шаблонов функций может быть полезна. Например, что произойдет, если мы попытаемся использовать наш шаблон класса Repository с типом данных char*:

Оказывается, вместо вывода имени пользователя, repository.print() выведет мусор! Почему?

При создании экземпляра шаблона для типа char*, конструктор Repository<char*> выглядит следующим образом:

Другими словами, это просто присваивание указателя (поверхностное копирование)! В результате m_value указывает на ту же область памяти, что и переменная string. А когда мы удаляем string в main(), мы удаляем значение, на которое указывает и m_value! Таким образом, происходит утечка памяти и мы получаем мусор при попытке вывода m_value.

К счастью, мы можем это исправить, используя явную специализацию шаблона функции. Вместо копирования указателя на string, нам нужно, чтобы конструктор копировал само значение string. Напишем отдельный конструктор для типа данных char*, который будет именно это делать:

Теперь, при выделении переменной типа Repository<char*> именно этот конструктор будет использоваться вместо стандартного. В результате m_value получит свою собственную копию string. Следовательно, когда мы удалим string в main(), m_value это никак не заденет.

Однако теперь класс имеет утечку памяти для типа char*, поскольку m_value не будет удален, когда переменная repository выйдет из области видимости. Как вы уже могли догадаться, это также можно решить, сделав отдельный деструктор для типа char*:

Теперь, когда переменные типа Repository<char*> выйдут из области видимости, память, выделенная в специальном конструкторе, будет удалена в специальном деструкторе.

Хотя во всех примерах выше мы работаем с методами класса, вы также можете аналогично выполнять явную специализацию шаблонов обычных функций (которые не являются методами классов).

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (8 оценок, среднее: 4,38 из 5)
Загрузка...

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *