Что такое шаблонный класс и шаблонная функция
Лекция 8. Шаблоны
1 шаблоны функций и классов
Понятие шаблона
Шаблоны обеспечивают непосредственную поддержку обобщенного программирования (т.е. c использованием типов в качестве параметров)
Шаблон зависит только от тех свойств параметра-типа, которые он явно использует
Существуют шаблоны функций и классов
Инстанцирование
Процесс порождения функции или класса из шаблона называется инстанцированием
Процесс генерации объявления класса по шаблону класса и аргументу шаблона
Версия шаблона для конкретного аргумента шаблона называется специализацией
1.1 Шаблоны функций
Для создания шаблона используется ключевое слово template. Также указывается пока неопределенный тип T.
Рассмотрим пример шаблонной функции swap для обмена значений двух переменных
Для получения функции проведем инстанцирование
Еще один пример: функция сортировки
Пример шаблона с целочисленным параметром
Примеры использования шаблонов
1.2 Шаблоны классов
Аналогично функциям можно создавать шаблоны классов. Рассмотрим пример стека:
Воспользоваться шаблоном класса можно так
Описание конструктора и деструктора шаблонного класса
Описание методов push и pop
Описание методов определения размера стека
Примеры инстанцирования
Рассмотрим примеры использования шаблона стека
1.3 Параметры шаблонов
Параметры шаблона
У шаблонов могут быть параметры различных типов
Инстанцирование выполняется с указанием значения параметра
В шаблонах допускается использование различных видов параметров
Если в шаблоне класса или функции необходимо использовать один и тот же шаблон, но с разными параметрами, то используются параметры-шаблоны. Например:
Перегрузка шаблонов
Специализация шаблонов
Явное инстанцирование
Явное инстанцирование используется
2 Специализация шаблонов
Для чего нужна специализация шаблонов? Для того чтобы задать шаблон для отдельного значения параметра (типа или значения).
В приведённом примере создаётся шаблон функции сравнения двух элементов одного типа. Так можно сравнивать любые числа, но не строки.
Для строк создаётся специализация.
2.1 Класс Bag
Шаблон класса Bag
В следующем примере приводится шаблон класса Bag, который является
динамическим контейнером элементов и его специализация,
позволяющая задавать элементы не по значению, а по указателю.
2.2 Пример с наследованием
Мы можем использовать специализацию при наследовании.
Рассмотрим следующую ситуацию. Допустим мы хотим создать класс, услуги которого базируются на сходных по назначению, но отличных в их реализации классах BaseA и BaseB. Если оба базовых класса обладают схожим интерфейсом, то логично оформить наш класс в виде шаблона:
если разработчику необходима информация о базовых классах объектов, основанных на шаблоне Derived. Как ее получить?
Следующий метод основан на введении вспомогательного шаблона класса (или структуры) BaseClassInstance, не содержащего ничего, кроме статической константы типа BaseClass, и специализированного для разных фактических типов базовых классов.
Описываем шаблон класса-наследника, в который помещается метод GetBaseClass
Вот так можно воспользоваться созданными шаблонами:
Шаблоны и шаблонные функции в C++. Введение
Шаблонные функции
Давайте рассмотрим простой пример. Допустим, у нас есть функция, которая меняет местами значения двух переменных типа int:
Теперь, допустим, у нас в функции main так же есть две переменные типа double, значения которых тоже нужно обменять. Функция для обмена значений двух переменных типа int нам не подойдет. Напишем функцию для double:
И теперь перепишем main:
Как видите, у нас алгоритм абсолютно одинаковый, отличаются лишь типы параметров и тип переменной temp. А теперь представьте, что нам еще нужны функции для short, long double, char, string и еще множества других типов. Конечно, можно просто скопировать первую функцию, и исправить типы на нужные, тогда получим новую функцию с необходимыми типами. А если функция будет не такая простая? А вдруг потом еще обнаружится, что в первой функции была ошибка? Избежать всего этого можно, например, «шаманством» с препроцессором, но это нам ни к чему, нам помогут шаблоны.
Для начала, заглянем в википедию и посмотрим, что же такое шаблоны:
Шабло́ны (англ. template) — средство языка C++, предназначенное для кодирования обобщённых алгоритмов, без привязки к некоторым параметрам (например, типам данных, размерам буферов, значениям по умолчанию).
https://ru.wikipedia.org/wiki/Шаблоны_C++
Итак, описание шаблона начинается с ключевого слова template за которым в угловых скобках (« ») следует список параметров шаблона. Далее, собственно идет объявление шаблонной сущности (например функция или класс), т. е. имеет вид:
Теперь давайте напишем шаблонную функцию my_swap. Исходя из упомянутой выше структуры объявления шаблона следует, что наша функция будет выглядеть так:
typename в угловых скобках означает, что параметром шаблона будет тип данных. T — имя параметра шаблона. Вместо typename здесь можно использовать слово class: template В данном контексте ключевые слова typename и class эквивалентны (лично мне больше нравится typename, а кому-то class). Далее, в тексте шаблона везде, где мы используем тип T, вместо T будет проставляться необходимый нам тип.
теперь давайте напишем функцию main:
Таким образом, если мы инстанцируем этот шаблон три раза с разными типами, то компилятор создаст три разные функции
Вывод типа шаблона исходя из параметров функции
На самом деле, мы можем вызвать функцию my_swap не указывая тип в угловых скобках. В ряде случаев компилятор может это сделать за вас.
рассмотрим вызов функции без указания типа:
Наша шаблонная функция принимает параметры типа T&, основываясь на шаблоне, компилятор видит, что Вы передаете в функцию аргументы типа int, поэтому может самостоятельно определить, что в данном месте имеется ввиду функция my_swap с типом int. Это deducing template arguments. Теперь давайте напишем пример посложнее. Например, программу сортировки массива(будем использовать сортировку «пузырьком»). Естественно, что алгоритм сортировки один и тот же, а вот типы элементов в массиве будут отличаться. Для обменивания значений будем использовать нашу шаблонную функцию my_swap. Приступим:
Source arrays: 10 5 7 3 4 7.62 5.56 38 56 9 Sorted arrays: 3 4 5 7 10 5.56 7.62 9 38 56
Как видите, компилятор сам генерирует out_array для необходимого типа. Так же он сам генерирует функцию bubbleSort. А в bubbleSort у нас применяется шаблонная функция my_swap, компилятор сгенерирует и её код автоматически. Удобно, не правда ли?
Введение в шаблонные классы
Шаблонными могут быть не только функции. Рассмотрим шаблонные классы. Начнем с простого примера. Мы добавим в наш предыдущий код функцию, которая будет искать максимум и минимум в массиве. При создании функции «упираемся» в проблему — как вернуть два указателя? Можно передать их в функцию в качестве параметров, а можно вернуть объект, который будет содержать в себе два указателя. Первый вариант при большом кол-ве возвращаемых значений приведет к заваливанию функции параметрами, поэтому я предлагаю сделать структуру:
А какого же типа будут указатели? Можно сделать их void*, но тогда придется постоянно кастовать их к нужному типу, и код станет похож на «Доширак». А что, если сделать эту структуру шаблонной? Попробуем:
Теперь компилятор при виде кода my_pointer_pair сам сгенерирует нам код структуры с соответствующими типами. В данном примере указатели у нас будут одинакового типа, но структуру мы сделаем такой, чтобы типы указателей могли быть разными. Это может быть полезно в других примерах (в данном случае я просто хотел показать, что у шаблона может быть не только один параметр).
Компилятор не будет автоматически определять типы для шаблона класса, поэтому необходимо их указывать самостоятельно.
Теперь давайте напишем код шаблонной функции для поиска максимума и минимума:
Теперь мы можем вызывать данную функцию:
Для классов мы должны явно указывать параметры шаблона. В стандарте C++11, устаревшее ключевое слово auto поменяло свое значение и теперь служит для автоматического вывода типа в зависимости от типа инициализатора, поэтому мы можем написать так:
Предлагаю написать еще одну функцию, которая будет выводить объект my_pointer_pair в стандартный поток вывода:
Теперь соберем всё воедино:
Arrays: 10 5 7 3 4 7.62 5.56 38 56 9 min = 3 max = 10 min = 5.56 max = 56
Шаблоны и STL
В комплекте с компилятором Вам предоставляется стандартная библиотека шаблонов (Standart Template Library). Она содержит множество шаблонных функций и классов. Например, класс двусвязного списка(list), класс «пара» (pair), функция обмена двух переменных(swap), функции сортировок, динамически расширяемый массив(vector) и т.д. Всё это — шаблоны и Вы можете их использовать. Для небольшого примера возьмем std::vector:
Заметьте, когда писали std::vector, авторы понятия не имели, элементы какого типа Вы будете хранить.
Шаблоны это слишком большой и мощный инструмент и описать всё в одной статье не представляется возможным. Это было лишь небольшое введение в мир шаблонов. Углубляясь в шаблоны, Вы поразитесь тому, какой мощный это инструмент и какие возможности он предоставляет.
Шаблоны (C++)
Шаблоны служат основанием для универсального программирования на C++. В качестве строго типизированного языка C++ требует, чтобы все переменные имели конкретный тип, либо явно объявленный программистом, либо выведенный компилятором. Однако многие структуры данных и алгоритмы выглядят одинаково независимо от типа, на котором они работают. Шаблоны позволяют определить операции класса или функции и предоставить пользователю указание конкретных типов, с которыми должны работать эти операции.
Определение и использование шаблонов
Шаблон — это конструкция, которая создает обычный тип или функцию во время компиляции на основе аргументов, предоставленных пользователем для параметров шаблона. Например, можно определить шаблон функции следующим образом:
В других случаях пользователь может объявить экземпляр шаблона, специализированный для типа int. Предположим, что get_a () и get_b () — это функции, возвращающие int:
Однако, поскольку это шаблон функции, и компилятор может вывести тип T из аргументов T и b, можно вызвать его так же, как обычная функция:
Когда компилятор встречает последнюю инструкцию, он создает новую функцию, в которой каждое вхождение T в шаблоне заменяется на :
Правила, определяющие, как компилятор выполняет выведение типов в шаблонах функций, основаны на правилах для обычных функций. Дополнительные сведения см. в разделе разрешение перегрузки вызовов шаблона функции.
Параметры типа
В minimum приведенном выше шаблоне Обратите внимание на то, что параметр типа minimum не определен каким-либо образом, пока он не будет использован в параметрах вызова функции, где добавляются квалификаторы Const и Reference.
Практически не существует ограничения на количество параметров типа. Несколько параметров разделяются запятыми:
Ключевое слово class эквивалентно typename в данном контексте. Предыдущий пример можно выразить следующим образом:
Оператор с многоточием (. ) можно использовать для определения шаблона, принимающего произвольное число из нуля или более параметров типа:
Будет создана ошибка компилятора, так как не MyClass предоставляет перегрузку для . Обратите внимание, что аргументы должны быть указателями
Параметры, не являющиеся типами
В отличие от универсальных типов на других языках, таких как C# и Java, шаблоны C++ поддерживают Параметры, не являющиеся типами, также называемые параметрами значений. Например, можно предоставить постоянное целочисленное значение, чтобы указать длину массива, как в этом примере, подобно классу std:: Array в стандартной библиотеке:
Обратите внимание на синтаксис в объявлении шаблона. size_t Значение передается в качестве аргумента шаблона во время компиляции и должно быть const или constexpr выражением. Используйте его следующим образом:
Другие виды значений, включая указатели и ссылки, могут передаваться как параметры, не являющиеся типами. Например, можно передать указатель на функцию или объект функции для настройки некоторой операции внутри кода шаблона.
Выведение типа для параметров шаблона, не являющихся типами
в Visual Studio 2017 и более поздних версиях, а /std:c++17 также в режиме или более поздней версии компилятор выводит тип аргумента шаблона, не являющегося типом, который объявлен с помощью auto :
Шаблоны в качестве параметров шаблона
Шаблон может быть параметром шаблона. В этом примере MyClass2 имеет два параметра шаблона: параметр typeName T и параметр шаблона arr:
Аргументы шаблона по умолчанию
Шаблоны классов и функций могут иметь аргументы по умолчанию. Если шаблон имеет аргумент по умолчанию, его можно оставить неопределенным при его использовании. Например, шаблон std:: Vector имеет аргумент по умолчанию для распределителя:
В большинстве случаев класс по умолчанию std:: распределитель приемлем, поэтому вы используете такой же вектор:
Но при необходимости можно указать пользовательский распределитель следующим образом:
При наличии нескольких аргументов шаблона все аргументы после первого аргумента по умолчанию должны иметь аргументы по умолчанию.
При использовании шаблона, параметры которого заданы по умолчанию, используйте пустые угловые скобки:
Специализация шаблонов
В некоторых случаях невозможно или нежелательно, чтобы шаблон определял точно такой же код для любого типа. Например, может потребоваться определить путь кода, который будет выполняться, только если аргумент типа является указателем или std:: wstring или типом, производным от конкретного базового класса. В таких случаях можно определить специализацию шаблона для этого конкретного типа. Когда пользователь создает экземпляр шаблона с этим типом, компилятор использует специализацию для создания класса, а для всех остальных типов компилятор выбирает более общий шаблон. Специализации, в которых все параметры являются специализированными, являются полными специализациями. Если только некоторые из параметров являются специализированными, это называется частичной специализацией.
Шаблон может иметь любое количество специализаций, если каждый специализированный параметр типа уникален. Только шаблоны классов могут быть частично специализированными. Все полные и частичные специализации шаблона должны быть объявлены в том же пространстве имен, что и исходный шаблон.
Дополнительные сведения см. в разделе специализация шаблонов.
Шаблоны классов
В этом разделе описываются правила, относящиеся к шаблонам классов C++.
Функции элементов в шаблонах классов
Функции-члены могут быть определены как внутри шаблона класса, так и за его пределами. В последнем случае они определяются как шаблоны функций.
Обратите внимание, что как и в функциях-членах класса шаблона, определение функции-члена для конструктора класса подразумевает, что список аргументов шаблона приводится дважды.
Функции-члены сами могут быть шаблонами функций, в которых указываются дополнительные параметры, как показано в следующем примере.
Шаблоны вложенных классов
Шаблоны можно определить в классах или шаблонах классов (в этом случае они называются шаблонами членов). Шаблоны членов, которые являются классами, называются шаблонами вложенных классов. Шаблоны элементов, которые являются функциями, обсуждаются в шаблонах функций элементов.
Шаблоны вложенных классов объявляются как шаблоны классов внутри области внешнего класса. Их можно определить во включающем классе или вне его.
В следующем примере кода демонстрируется шаблон вложенного класса внутри обычного класса.
Локальные классы не могут иметь шаблоны элементов.
Друзья в шаблоне
Шаблоны классов могут иметь друзей. Дружественными объектами класса-шаблона могут быть классы или шаблоны классов, функции или шаблоны функций. Ими также могут быть специализации (кроме частичных) шаблонов классов или шаблонов функций.
В следующем примере дружественная функция определена как шаблон функции в шаблоне класса. Этот код создает по одной версии дружественной функции для каждого экземпляра шаблона. Такую конструкцию можно использовать в тех ситуациях, когда дружественная функция зависит от тех же параметров шаблона, что и класс.
В следующем примере создается дружественная функция, которая имеет специализацию шаблона. Если исходный шаблон функции является дружественным объектом, то и его специализация автоматически становится дружественной.
Кроме того, как отмечается в комментарии перед объявлением дружественной функции в следующем коде, вы можете взять только специализированную версию шаблона и объявить ее в качестве дружественной. В этом случае определение дружественной специализации шаблона необходимо поместить за пределами класса шаблона.
В следующем примере показан дружественный шаблон класса, объявленный в пределах шаблона класса. Затем этот шаблон класса используется в качестве аргумента шаблона для дружественного класса. Дружественные шаблоны классов должны определяться за пределами шаблона класса, в котором они объявлены. Все специализации или и частичные специализации дружественного шаблона также являются дружественными объектами исходного шаблона класса.
Повторное использование параметров шаблона
Параметры шаблона могут повторно использования в списке параметров шаблона. Например, приведенный ниже код допустим:
Основы шаблонов С++: шаблоны функций
Дисклаймер: статья была начата еще в феврале, но, по зависящим от меня причинам, закончена не была. Тема очень обширна, поэтому публикуется в урезанном виде. Что не поместилось, будет рассмотрено позже.
Невозможно разбираться в современном С++, не зная, что такое шаблоны программирования. Данное свойство языка открывает широкие возможности оптимизации и повторного использования кода. В данной статье попробуем разобраться, что это такое и как это всё работает.
Механизм шаблонов в языке С++ позволяет решать проблему унификации алгоритма для различных типов: нет необходимости писать различные функции для целочисленных, действительных или пользовательских типов – достаточно составить обобщенный алгоритм, не зависящий от типа данных, основывающийся только на общих свойствах. Например, алгоритм сортировки может работать как с целыми числами, так и с объектами типа «автомобиль».
Существуют шаблоны функций и шаблоны классов.
Рассмотрим более подробно шаблоны функций.
Шаблоны функций
Как написать первую шаблонную функцию?
Рассмотрим случай определения минимального элемента из двух. В случае целых и действительных чисел придется написать 2 функции.
Можно, конечно, реализовать только одну функцию, с действительными параметрами, но для понимания шаблонов это будет вредным.
Что произойдёт в случае компиляции приложения? Обе реализации функции попадут в бинарный код приложения, даже если они не используются (впрочем, сейчас компиляторы очень умные, умеют вырезать неиспользуемый код). А если необходимо добавить функцию, определяющую минимальную из 2 строк (сложно представить без уточнения, что есть минимальная строка)?!
В этом случае, если алгоритм является общим для типов, с которыми приходится работать, можно определить шаблон функции. Принцип, в общем случае, будет следующим:
Самым интересным является тот факт, что пока нет вызова функции min, при компиляции она в бинарном коде не создается (не инстанцируется). А если объявить группу вызовов функции с переменными различных типов, то для каждого компилятор создаст свою реализацию на основе шаблона.
Вызов шаблонной функции, в общем, эквивалентен вызову обыкновенной функции. В этом случае компилятор определит, какой тип использовать вместо Type, на основании типа фактических параметров. Но если подставляемые параметры окажутся разных типов, то компилятор не сможет вывести (инстанцировать шаблон) реализацию шаблона. Так, в ниже следующем коде компилятор споткнётся на третьем вызове, так как не может определить, чему равен Type (подумайте, почему?):
Решается эта проблема указанием конкретного типа при вызове функции.