Шаблоны. Специализации шаблонов класса в С++.

  • Для тех, у кого Visual Studio, может пригодится вот это видео:
Иногда бывают ситуации, где имеется эквивалентная функциональность. В простейшем случае такой ситуацией может оказаться операция сложения: десятичные числа складываются своим способом, строки своим, а какие-нибудь римские цифры своим. Несмотря на то, что способы разные, мы используем одну и ту же форму записи: для сложения обычно используется +. Могут быть совершенно разные задачи, где возникнет вопрос эквивалентных реализаций, специализированных под определённые типы. Для решения этой проблемы нам дана возможность специализировать шаблоны. Это значит, что если для функций или переменных класса определено наиболее общее поведение, но в класс попадает тип, поведение которого разнится с существующим, мы должны специализировать шаблон. Можно специализировать или отдельную функцию или сам класс.
  • Возможность специализации класса существует для того, чтобы мы могли скорректировать поведение чего-либо под нужды неподходящего наиболее общему решению типа.
  • Чтобы специализировать шаблон класса, следует объявить класс, предварив его конструкцией template< >, и указать типы, для которых специализируется шаблон класса. Типы используются в качестве аргументов шаблона и задаются непосредственно после имени класса.


  • Специализированные функции перекрывают выполнение обобщённой функции, а специализированные классы перекрывают описание обобщённого класса.
Факт перекрытия увидеть несложно. Давайте напишем класс, который будет уметь менять местами объекты некоторых типов.

Объекты меняются местами. Всё хорошо. Для понимания листинга #2 вам нужно уметь писать нешаблонную функцию, посредством которой объекты будут меняться местами. И иметь понимание об обычных шаблонных функциях. T1 у меня символизирует один вид типа, а T2 второй вид типа. Поскольку в коде я использую два вида типов: int и double, которые отдаю функции, то у меня два символических обозначения.
Давайте попробуем поменять местами две С строки.

Из-за того, что нельзя массив в массив, наш алгоритм перемены местами объектов поломался. Мы можем переписать шаблонную функцию смены таким образом, чтобы менялись такие символьные массивы, но тогда нормально перестанут меняться обычные числовые типы. Это и есть возникновение ситуации необходимости специализации шаблона. Нам необходимо описать эквивалент выполнению функции, описанной внутри шаблонного класса, что как раз и есть специализация. Давайте специализируем поведение для массива символов.

Несмотря на то, что листинг #3 компилируется и программа работает, если внимательно присмотреться, то будет видно, что целочисленные переменные остались на своих стартовых позициях. И перед нами возникает делемма: используем ссылки, не можем поменять местами массивы символов, не используем ссылки, не можем поменять местами обычные численные переменные. Для начала нужно определиться, почему при наличии параметров-ссылок в функции обмена внутри нашего класса код отказывается компилироваться. Первопричина в том, что массивы указателями не являются, и когда в функцию приходит массив название пришедшего массива неявным образом приводится к указательной переменной на первый его элемент (параметр указатель, массив приводится к указателю). Любые преобразования типов не переделывают оригинальный объект, а на основе оригинального создают клон-переменную (временную переменную), но с выбранным типом. Когда у нас параметры-ссылки, то параметр-ссылка пытается связаться с созданным приведением временным указателем. Правила С++ запрещают использовать ссылки на временные объекты, можно свзывать константные ссылки, но нам константные не подходят, а обычные мы связать не имеем права.
Вариантов решения существует несколько: специализация непосредственнно под массив, специализация под указатели, где указатели будут интерпретироваться как массивы. Для начала напишем вариант, где массивы будут масивами (он полегче к пониманию):

В листинге #4 можно наблюдать, что в параметре раскрываемого класса указывается непосредственно тот тип, ради которого проводится специализация. А вот в параметрах функции ссылки на массив могут кому-то показаться причудливыми, странными, необычными. Никто не запрещает создавать ссылки на массив, а поскольку у нас прототип меняющей функции предполагает приём в параметры ссылок, то нам нужны ссылки, а коли ссылки, то просто массив не подойдёт, нужно ссылку на массив. Как можно увидеть, теперь специализированная для массива функция перекрывает наиболее обобщённый вариант реализации обмена своей реализацией. Если так случается, что эта специализированная функция наиболее подходит для обмена (а она лучше всего подходит, когда у нас меняются местами два массива ёмкостью в 255 элементов), то она и выполняется. Но есть в этом способе и неудобство: а что если у массивов разная ёмкость? Не всегда ведь массив будет вместимостью 255. И для каждой новой ёмкости может прийтись писать отдельную специализацию. Мы написали для 255, потом напишем для 100, для 22 и т. д. Это достаточно неудобно. Хотя возможны ситуации, где достаточно специализировать именно массив, всякое случается. Но для нас лучше подойдёт наиболее общая форма, где мы не будем зависеть от количества элементов, умещающихся в массив.
Изменение задачи в выбранном направлении требует пересмотрения к написанию кода. Мы можем использовать указатель как массив, но, как ранее было выяснено, не можем связать ссылку с временным объектом-указателем, получаемым как результат неявного преобразования типов. Поскольку всё упирается в ссылку, которая не хочет связываться с указателем, полученным из неявного приведения названия массива, нам необходимо избавиться от ссылочности. Нам бы могли помочь контантные ссылки, такие ссылки имеют право быть связанными со временными объектами, но мы меняем объекты, поэтому о константности речи идти не может. Ограничение ссылочности параметров, с которым мы столкнулись, можно обойти путём специализации непосредственно класса, а не отдельной его внутренней функции.Специализация класса позволит посредством объекта класса объяснять компилятору, что аргументы функции нужны или такие-то, или такие-то. Мы объясним, что если в класс на обработку уходит массив, то наша функция должна будет принимать не ссылки, а обычные параметры (но с учётом того, что массив умеет приводится у указателю, параметры будут указателями, но не ссылками на указатели). Проделать это можно вот таким, например, образом:

Специализация всего класса позволила нам избавиться от ссылочности параметров внутриклассовой функции и благодаря этому мы смогли решить задачу по написанию класса-обменщика. Теперь объекты класса-обменщика умеют менять местами и числовые типы и массивы символов и С-строки (любые строковые представления в виде массивов символов, на которые может указывать какой-нибудь указатель). Основные моменты, которые могут спровоцировать вопросы, описаны в комментариях листинга.
  • При специализации шаблона класса необходимо специализировать все его функции-члены.

Если вам хочется попробовать что-то своими силами, добавьте в листинг #5 специализацию обменщика обычного массива, эта задача очень похожа на уже решённую, но в обычном массиве нет признака конца, поэтому имеется небольшое отличие.
Буду надеяться, что эта тема помогла вам разобраться и оказалось полезной.
Статья полностью переписана 15.05.2018г.

2 комментария на «“Шаблоны. Специализации шаблонов класса в С++.”»

  1. Слава (мне):

    Привет, дорогой автор!

    Почему в этом  фрагменте должна быть проблема в первой строчке ?
    MyClass<const char*> X2(«Hello»,»Bye»); //<— Заработало
    std::cout<<X2.Add();
    Специализируется ведь вторая строчка?

    • Да. Специализируется ради std::cout << X2.Add();

      Конечно, проблемы быть не должно. У меня в коде переопределение X2. Сбило с толку, наверное, когда писал.
      Позже будет исправлено.

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

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

Поиск

 
     

Случайная книга в электронном формате

https://www.litres.ru/vitaliy-potopahin/turbo-pascal-reshenie-slozhnyh-zadach-2/?lfrom=15589587
Яндекс.Метрика