8. Шаблоны функций и классов в С++.
Параметры шаблонов. Специализация шаблонов частичная и полная. Параметры шаблона задаваемые по умолчанию. Шаблоны с переменным числом параметров. Пространства имен.
Шаблоны
В языке Си приходилось создавать подобные функции, работающие с разными типами данных. По существу был copy-past кода под разные типы данных. Можно решать эту проблему с помощью макросов с параметрами, но это крайне опасная вещь. На этапе препроцесирования происходит подстановка макроса в код, на этапе компиляции идет проверка подставленного. Если у нас ошибка в макросе, то определить её крайне сложно.
Макрос с параметрами объявляется следующим образом:
Примеры:
В языке C++ эта проблема подобного копирования кода решается с помощью шаблонов. Мы можем определить шаблон. Это может быть функция, класс, метод класса и также мы можем определить шаблон имени типа. Во время компиляции будет подстановка значения параметров шаблона с проверкой шаблона.
Понятие шаблона
Шаблон не является ни классом, ни функцией. Функция или класс генерируется на основе шаблона во время использования. Компилятор встречает вызов функции, смотрит, может ли использоваться шаблон, и, если может, создаёт по шаблону функцию или класс.
Параметрами шаблона могут быть:
Типы. Параметрами типа могут быть простые типы языка си, производные типы языка си и классы.
Параметры значений. Параметры значения - только константные параметры целого типа или указатели с внешним связыванием.
Шаблоны можно определять:
функций
типов
классов
методов класса
Синтаксис шаблона в общем виде:
Про typename и class
Для параметра типа изначально использовалось ключевое слово class
, но программисты стали возражать, так как в С++ простые типы изначально не были классами, и разработчик языка добавил еще одно ключевое слово - typename
. Разницы между описанием class
и typename
нет, это сделано для того, чтобы лучше читался код, чтобы было понимание, что это тип.
Шаблоны функций и типов
Шаблоны функций
Возможно два варианта создания функции по шаблону:
Функция принимает параметры шаблона, например,
void freeArray(Type* arr)
;Явное указание параметров функции, например,
Type* initArray(int count)
;
Если функция принимает параметры шаблона, то компилятор может сгенерировать функцию по её вызову в зависимости от того, какого типа параметры вы передаете.
Определение функции представляет из себя то же самое, что объявление, только с телом. В коде параметры шаблона мы используем как типы, и при создании функции по шаблону это имя заменяется конкретным типом.
Шаблоны типов
Мы можем определять не только функций, но и шаблонный тип. В языке Си для задания имени типа использовался typedef
. Это аналог определения какой-либо переменной, только вместо этого - имя типа.
Например, определим имя для типа, принимающего указатель на функцию, принимающую int
и возвращающую void
.
В языке С++ шаблон на основе typedef
мы определить не можем. Добавляется еще одна конструкция - using
, выполняющая ту же функцию, что и typedef
. После using
записываем имя типа, а далее записывается так называемый абстрактный описатель (определение переменной без ее имени).
Пример:
Две вышеприведенные записи с typedef
и using
эквивалентны, но для typedef
мы не можем определить шаблон.
Пример шаблона функции и вызова функции
Пример использования шаблонов функций и типа
Правило вызова шаблонных функций
Любую функцию можно:
Перегрузить.
Определить на основе шаблона с конкретными значениями параметров (или частичная, или полная специализация). Специализация тоже является шаблоном.
Правило вызова.
Компилятор рассматривает, может ли он вызвать перегруженную функцию. Если он находит перегруженную функцию, он ее вызывает. Если он не нашел ни одной перегруженной функции, он рассматривает специализации и подбирает подходящую. Если не подошла ни одна специализация, компилятор использует шаблон. То есть, шаблонная функция используется только в том случае, если для вызова функции компилятор не смог подобрать подходящую перегруженную функцию или функцию со специализацией.
Пример на правило вызова
Проблема определения возвращаемого типа
На вызов и создание по шаблону конкретной функции влияют только параметры, которые мы передаём. Как определять возвращаемый тип? Возвращаемый тип можно определять по какому-либо выражению.
В заголовке пишем -> decltype(выражение)
. Происходит приведение к какому-либо типу этого выражения. Вместо конкретного типа ставим модификатор auto
.
Автоматический модификатор auto
мы так же можем использовать для какой-либо переменной. Единственное правила: мы обязательно должны эту переменную инициализировать. Тип этой переменной определяется типом возвращаемого значения выражения.
В данном случае шаблон принимает два параметра. По шаблону создается функция, принимающая первый параметр типа int и второй параметр типа double
. При сложении целого с вещественным происходит приведение к типу double, decltype
возвращает тип double
, соответственно тип возвращаемого значения тоже будет double, а значит переменная s тоже типа
double`.
Пример:
Механизм очень удобный, и часто его используют чтобы не писать слишком длинные типы.
Шаблоны классов и методов
Шаблоны классов
Определение шаблона класса:
Мы определили шаблон класса. Это не класс, это шаблон класса. Методы шаблона класса также являются шаблонами. В принципе, у нас может быть нешаблонный класс, но с шаблонными методами, то есть в обычном классе можно определить шаблонный метод.
Методы шаблонного класса
Как определяются методы шаблонного класса?
Мы должны указать класс, но у нас класса нет, у нас шаблон (ловушка), поэтому нужно указать параметры шаблона (в данном случае у нас один параметр типа T
).
Так же, как и у любой функции, у шаблона метода может быть специализация.
Для вызовов методов действует такое же правило, как и для функций: сначала для созданного класса, если есть специализация выбирается специализация, если нет специализации, создается метод по шаблону.
Как определить класс?
При определении объектов класса, будет по шаблону создаваться класс, поэтому мы должны указать для класса параметры A<float> obj;
. По этому шаблону будет создан класс, в котором T
заменится на float
.
Кроме параметров типа, у нас могут быть параметры значений (объекты с внешним связыванием, целочисленные константы или тип перечисление).
Пример:
Пример с параметрами типа и параметром значений
В примере два параметра класса: параметр типа и параметр значений.
Есть ли какая-то проблема с параметрами значений? По шаблону у нас создаются классы, и эти классы (хотя шаблон один и тот же) будут между собой никак не связаны. Это будут разные классы, разный тип.
Если у нас разные параметры типов, это можно понять: массив целых чисел, вещественных, комплексных... возникает вопрос, можем ли мы совместно использовать эти типы? Можем ли мы массиву целых значений присвоить массив комплексных или наоборот? Возникает вопрос, как быть с параметрами значений.
Под каждый параметр значений, в данном случае, по шаблону будет создаваться свой класс. Массив из 2ух элементов и из 10и - разные классы. Чаще всего это не нужно. С параметрами значений нужно быть осторожными. Лучше всего сначала проанализировать, нужен ли параметр значений.
Специализация шаблонов класса
Специализация может быть как частичная, так и полная.
Полная специализация
В данном случае описана полная специализация шаблона класса А:
Специализация является тоже шаблоном. По специализации так же создается класс, если нужно. У нас создаются классы по специализации. Специализация может иметь отличные параметры от шаблонов, вернее тело специализации может быть другим: другие члены, данные, методы, отличные от самого шаблоны.
Пример полной специализации
У нас есть шаблон класса А и специализация этого класса А. В специализации класса интерфейс, отличный от шаблона.
Частичная специализация
Частичная специализация - когда мы указываем не все значения параметра шаблона. При частичной специализации шаблонов возможна неоднозначность при вызове. Здесь аналогичный подход с функциями: сначала идет выбор специализации, если невозможно создать класс по специализации, создается класс по шаблону.
Пример частичной специализации
У нас есть класс с двумя параметрами шаблонов. У шаблона класса два параметра class A<T, T>
, но эти два параметра одного типа template <typename T>
. Мы можем частично специализировать этот шаблон.
Параметры по умолчанию
Так же, как при определении функций, параметры шаблона могут быть по умолчанию. В случае выше сам шаблон имеет один параметр по умолчанию - double
. Если мы передаем только один параметр, будет вызываться этот шаблон (а второй параметр по умолчанию типа doule):
Шаблоны с переменным числом параметров
Так же как функции, шаблоны мы можем создавать с переменным числом параметров. Это могут быть шаблоны функций, методов и классов.
Когда удобно использовать
Если мы рассматриваем для классов, чтобы в классах были инициализаторы с несколькими параметрами. В принципе, можно использовать initializer_list
, если параметры одного типа. А если разного?
И, если говорить о классах, используются так называемые кортежи, когда мы формируем какой-то тип из полей разных типов. В python кортеж - альтернатива и массиву, и структуре. По существу, мы можем рассматривать как массив с элементами разного типа. А зачем это нужно? Мы не всегда знаем то данное, которое нам нужно возвращать, какие параметры могут входить в это данное. Мы можем возвращать список данных, формировать структуру, причем можем, конечно, формировать список данных разного типа. Кортеж - хорошая альтернатива, когда мы объединяем по надобности параметры разного типа в одно данное.
Пример шаблона функции с переменным числом параметров
Как для функции, так и для классов, будет создаваться набор функций с разным количеством параметров.
В данном примере вызываем функцию sum()
и передаем в неё 5 параметров. По шаблону, будет вызываться функция с 5 параметрами. Но эта функция с 5 параметрами вызывает функцию sum()
, в которую мы передаем 4 параметра. Будет вызываться функция с 4 параметрами, потом с 3, с 2... тут мы должны поставить шаблон/специализацию/перегрузку функций, которые ограничат нам формирование функций по шаблону. В данном случае с одним параметром.
Обязательно нужно поставить ограничение на создание функций по шаблону! В примере выше это реализовано за счёт шаблона с одним параметром.
Пример шаблона с переменным числом параметров значений
Пример шаблона класса с переменным числом параметров. Рекурсивная реализация кортежа
Кортежи используются крайне редко.
С кортежами существуют две проблемы:
Рассматривать кортеж как объект нельзя, так как разные типы и определить перегруженные операции над ним невозможно.
Чёткое местоположение каждого элемента - нужно помнить, какие типы лежат внутри кортежа.
Last updated