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

  Юрий  | 

  |

  Обновл. 15 Сен 2021  | 

 36696

 ǀ   7 

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

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

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

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

Результат:

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 (167 оценок, среднее: 4,81 из 5)
Загрузка...

Комментариев: 7

  1. Mike:

    Здравствуйте , возник небольшой вопрос, по поводу специализации шаблона для функции print(), я хочу чтобы при вызове этой функции массив выводился, как я могу это сделать?

    1. Павел:

      Так он выводится

      Или так?

  2. Woland:

    К автору перевода и сайта конечно нет претензий, но автору книги урок стоило бы назвать: "Явная специализация шаблона функции класса". Ибо если есть шаблон и есть один тип который надо сделать по особому, то получается надо делать просто обычную перегруженную функцию. Других примеров здесь нет. Например если особая функция преобразует в теле число в строку через to_string.
    Вообще автор закрутил конечно свою фразочку: "Всё просто: записываем экземпляр шаблона функции (если функция является методом класса…". Прям как хитроопый юрист. А если не является?! То угадайте сами. Даже с начинающими программистами такие фразочки не проходят, ибо они быстро упрутся в проблему. Такое чувство, что он сам не знал как поступать в таком случае, но пытался это завуалировать.
    Стоило хотя бы написать, что может стоит в таком случае перегруженную функцию поставить первой, до шаблона. Чтобы компилятор сразу находил её, а не пытался создать её из шаблона. Но это всё теперь догадки.
    Может автор перевода уже знает больше? Было бы полезноно узнать.

  3. Dmitry:

    А еще есть функции strlen, strcpy, strncpy

    1. Александр:

      Для этих функций нужно заголовочный файл подключать, а оно нам надо?

      1. Александр:

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

  4. Oleg:

    Я бы сказал не утечка памяти, а dangling pointer.

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

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