На предыдущих уроках мы рассматривали, как с помощью функций и классов сделать программы удобнее, безопаснее и производительнее.
Шаблоны функций
Хотя функции и классы являются мощными и гибкими инструментами для эффективного программирования, в некоторых случаях они ограничены из-за требования C++ указывать типы всех используемых параметров. Например, предположим, что нам нужно написать функцию для вычисления наибольшего среди двух чисел:
1 2 3 4 |
int max(int a, int b) { return (a > b) ? a : b; } |
Всё отлично до тех пор, пока мы работаем с целочисленными значениями. А что, если нам придется работать и со значениями типа double? Вы, вероятно, решите перегрузить функцию max() для работы с типом double:
1 2 3 4 |
double max(double a, double b) { return (a > b) ? a : b; } |
Теперь у нас есть две версии одной функции, которые работают с типами char, int, double и, если мы перегрузим оператор >
, даже с классами! Однако, поскольку C++ требует, чтобы мы указывали типы наших переменных, нам приходится записывать несколько версий одной и той же функции, где единственное, что меняется — это тип параметров.
А это, в свою очередь, головная боль для программистов, так как поддерживать такой код непросто как по затраченным усилиям, так и по времени. И самое важное то, что это нарушает одну из концепций эффективного программирования — сократить до минимума дублирование кода. Правда, было бы неплохо написать одну версию функции max(), которая работала бы с параметрами ЛЮБОГО типа?
Это возможно. Добро пожаловать в мир шаблонов!
Если посмотреть определение слова «шаблон» в словаре, то увидим следующее: «Шаблон — это образец, по которому изготавливаются похожие изделия». Например, шаблоном является трафарет — объект (например, пластинка), в котором прорезан рисунок/узор/символ. Если приложить трафарет к другому объекту и распылить краску, то получим этот же рисунок, прилагая минимум усилий, быстро и, что не менее важно, мы сможем сделать десятки этих рисунков разных цветов! При этом нам нужен лишь один трафарет и нам не нужно определять цвет рисунка заранее (до использования трафарета).
В языке C++ шаблоны функций — это функции, которые служат образцом для создания других подобных функций. Главная идея — создание функций без указания точного типа(ов) некоторых или всех переменных. Для этого мы определяем функцию, указывая тип параметра шаблона, который используется вместо любого типа данных. После того, как мы создали функцию с типом параметра шаблона, мы фактически создали «трафарет функции».
При вызове шаблона функции, компилятор использует «трафарет» в качестве образца функции, заменяя тип параметра шаблона на фактический тип переменных, передаваемых в функцию! Таким образом, мы можем создать 50 «оттенков» функции, используя всего лишь один шаблон!
Создание шаблонов функций
Сейчас вам, вероятно, интересно, как создаются шаблоны функций в языке C++. Оказывается, это не так уж и сложно. Рассмотрим еще раз целочисленную версию функции max():
1 2 3 4 |
int max(int a, int b) { return (a > b) ? a : b; } |
Здесь мы трижды указываем тип данных: в параметрах a
, b
и в типе возврата функции. Для создания шаблона этой функции нам нужно заменить тип int на тип параметра шаблона функции. Поскольку в этом случае используется только один тип данных (int), то нам нужно указать только один тип параметра шаблона.
Мы можем назвать этот тип как угодно, главное, чтобы это не было зарезервированным/ключевым словом. В языке C++ принято называть типы параметров шаблонов большой буквой T
(сокр. от «Type»).
Вот наша переделанная функция max():
1 2 3 4 |
T max(T a, T b) { return (a > b) ? a : b; } |
Но это еще не всё. Программа работать не будет, так как компилятор не знает, что такое Т
!
Чтобы всё заработало, нам нужно сообщить компилятору две вещи:
Определение шаблона функции.
Указание того, что T
является типом параметра шаблона функции.
Мы можем сделать это в одной строке кода, выполнив объявление шаблона (а точнее — объявление параметров шаблона):
1 2 3 4 5 |
template <typename T> // объявление параметра шаблона функции T max(T a, T b) { return (a > b) ? a : b; } |
Эврика! Работает!
Рассмотрим детально объявление параметров шаблона:
Сначала пишем ключевое слово template, которое сообщает компилятору, что дальше мы будем объявлять параметры шаблона.
Параметры шаблона функции указываются в угловых скобках (<>
).
Для создания типов параметров шаблона используются ключевые слова typename и class. В базовых случаях использования шаблонов функций разницы между typename и class нет, поэтому вы можете выбрать любое из двух. Если вы используете ключевое слово class, то фактический тип параметров не обязательно должен быть классом (это может быть переменная фундаментального типа данных, указатель или что-то другое).
Затем называем тип параметра шаблона (обычно T
).
Если требуется несколько типов параметров шаблона, то они разделяются запятыми:
1 2 |
template <typename T1, typename T2> // Шаблон функции здесь |
Если параметров несколько, то их обычно называют T1
, T2
или другими буквами: T
, S
.
Примечание: Поскольку тип аргумента функции, передаваемый в тип T
, может быть классом, а классы, как правило, не рекомендуется передавать по значению, то лучше сделать параметры и возвращаемое значение нашего шаблона функции константными ссылками, например:
1 2 3 4 5 |
template <typename T> const T& max(const T& a, const T& b) { return (a > b) ? a : b; } |
Использование шаблонов функций
Использование шаблонов функций аналогично использованию обычных функций:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <iostream> template <typename T> const T& max(const T& a, const T& b) { return (a > b) ? a : b; } int main() { int i = max(4, 8); std::cout << i << '\n'; double d = max(7.56, 21.434); std::cout << d << '\n'; char ch = max('b', '9'); std::cout << ch << '\n'; return 0; } |
Результат:
8
21.434
b
Обратите внимание, все три вызова функции max() имеют параметры разных типов! Поскольку мы вызываем функцию max() с тремя разными типами параметров, то компилятор использует шаблон функции для создания трех разных версий функции max():
Версия с параметрами типа int (max<int>
).
Версия с параметрами типа double (max<double>
).
Версия с параметрами типа char (max<char>
).
Нам не нужно явно указывать тип передаваемых значений (часть <int>
в max<int>
), компилятор вычислит это самостоятельно.
Заключение
Шаблоны функций экономят много времени, так как шаблон мы пишем только один раз, а использовать можем с разными типами данных. Как только вы привыкнете к написанию шаблонов функций, вы обнаружите, что это по времени занимает не больше написания обычной функции (одной версии обычной функции). Шаблоны функций намного упрощают дальнейшую поддержку кода, и они более безопасные, так как нет необходимости выполнять вручную перегрузку функции, копируя код и изменяя лишь типы данных, когда нужна поддержка нового типа данных.
У шаблонов функций есть несколько недостатков, и было бы непростительно, если бы мы о них не поговорили:
Во-первых, некоторые старые компиляторы могут не поддерживать шаблоны функций или поддерживать, но с ограничениями. Однако сейчас это уже не такая проблема, как раньше.
Во-вторых, шаблоны функций часто выдают сумасшедшие сообщения об ошибках, которые намного сложнее расшифровать, чем ошибки обычных функций.
В-третьих, шаблоны функций могут увеличить время компиляции и размер кода, так как один шаблон может быть «реализован» и перекомпилирован в нескольких файлах.
Данные недостатки довольно незначительны по сравнению с мощью и гибкостью шаблонов функций!
Примечание: Стандартная библиотека C++ имеет в своем арсенале шаблон функции max() (который находится в заголовочном файле algorithm), поэтому вы можете не реализовывать эту функцию вручную в будущем. Кроме этого, если вы пишете свои собственные шаблоны функций и используете стейтмент using namespace std;
, то не забывайте о возможности возникновения конфликтов имен, так как компилятор не сможет определить, хотите ли вы использовать свою версию функции max() или версию std::max().
Тот случай когда шаблон не работает:
По идее всё нормально функция (ниже) вполне подходит для шаблонного типа. Но нет, шаблон не работает. Причём если бы в строке с параметрами в конце был бы ещё один параметр, например ", Type S", то всё работало бы. А всё этот typename в параметрах функции. Но без него шаблон с итераторами не заработает вообще. Обращаю внимание, что ругается именно на вызов такой функции, а не то, что выше.
Вот такой он ЦэПэПэ, с фокусами. И где тут AI при анализе кода? И это сегодня.
Может кому пригодится.
у меня первый вариант работает.
Второй тоже работает (Visual Studio 2022, C++14)