Шаблоны (C++)
Шаблоны являются основой для универсального программирования в C++. В качестве строго типизированного языка C++ требует, чтобы все переменные имели определенный тип, явно объявленный программистом или выводил компилятором. Однако многие структуры и алгоритмы данных выглядят одинаково независимо от типа, на который они работают. Шаблоны позволяют определить операции класса или функции и разрешить пользователю указать, какие типы этих операций должны работать.
Определение и использование шаблонов
Шаблон — это конструкция, которая создает обычный тип или функцию во время компиляции на основе аргументов, которые пользователь предоставляет для параметров шаблона. Например, можно определить шаблон функции следующим образом:
template T minimum(const T& lhs, const T& rhs)
Приведенный выше код описывает шаблон универсальной функции с одним параметром типа T, возвращаемое значение и параметры вызова (lhs и rhs) всех этих типов. Вы можете назвать параметр типа, который вы хотите, но по соглашению одноглавные буквы чаще всего используются. T является параметром шаблона; typename ключевое слово говорит, что этот параметр является заполнителем для типа. При вызове функции компилятор заменит каждый экземпляр T конкретным аргументом типа, заданным пользователем или выведенным компилятором. Процесс, в котором компилятор создает класс или функцию из шаблона, называется экземпляром шаблона. minimum Это экземпляр шаблона minimum .
В другом месте пользователь может объявить экземпляр шаблона, специализированного для int. Предположим, что get_a() и get_b() — это функции, возвращающие int:
int a = get_a(); int b = get_b(); int i = minimum(a, b);
Тем не менее, поскольку это шаблон функции, и компилятор может выводить тип T из аргументов a и b, можно вызвать его так же, как обычная функция:
int i = minimum(a, b);
При обнаружении последней инструкции компилятор создает новую функцию, в которой каждое вхождение T в шаблоне заменяется следующим int образом:
int minimum(const int& lhs, const int& rhs)
Правила того, как компилятор выполняет вычет типов в шаблонах функций, основаны на правилах для обычных функций. Дополнительные сведения см. в разделе «Разрешение перегрузки вызовов шаблонов функций».
Параметры типа
В приведенном minimum выше шаблоне обратите внимание, что параметр типа T не является соответствующим образом, пока он не будет использоваться в параметрах вызова функции, где добавляются квалификаторы констант и ссылочных квалификаторов.
Нет практического ограничения на количество параметров типа. Разделите несколько параметров запятыми:
template class Foo<>;
Ключевое слово class эквивалентен этому контексту typename . Вы можете выразить предыдущий пример следующим образом:
template class Foo<>;
Оператор многоточия (. ) можно использовать для определения шаблона, который принимает произвольное число параметров нулевого или более типа:
template class vtclass; vtclass < >vtinstance1; vtclass vtinstance2; vtclass vtinstance3;
Любой встроенный или определяемый пользователем тип можно использовать в качестве аргумента типа. Например, можно использовать std::vector в стандартной библиотеке для хранения переменных типа int , double std::string, const MyClass MyClass *, MyClass& и т. д. Основное ограничение при использовании шаблонов заключается в том, что аргумент типа должен поддерживать все операции, применяемые к параметрам типа. Например, если мы вызываем minimum использование MyClass , как в следующем примере:
class MyClass < public: int num; std::wstring description; >; int main() < MyClass mc1 ; MyClass mc2 ; auto result = minimum(mc1, mc2); // Error! C2678 >
Ошибка компилятора создается, так как MyClass не предоставляет перегрузку для оператора.
Нет никаких обязательных требований, что аргументы типа для любого конкретного шаблона принадлежат одной иерархии объектов, хотя можно определить шаблон, который применяет такое ограничение. Вы можете объединить объектно-ориентированные методы с шаблонами; Например, можно сохранить производный* в векторе. Обратите внимание, что аргументы должны быть указателями
vector vec; MyDerived d(3, L"back again", time(0)); vec.push_back(&d); // or more realistically: vector> vec2; vec2.push_back(make_shared());
Основные требования, std::vector которые и другие стандартные контейнеры библиотеки накладывают на элементы T , которые T можно назначить с помощью копирования и создания копирования.
Параметры, не относящиеся к типу
В отличие от универсальных типов на других языках, таких как C# и Java, шаблоны C++ поддерживают параметры, отличные от типов, также называемые параметрами значения. Например, можно указать целочисленное значение константы, чтобы указать длину массива, как и в этом примере, аналогичному классу std::array в стандартной библиотеке:
template class MyArray < T arr[L]; public: MyArray() < . >>;
Обратите внимание на синтаксис в объявлении шаблона. Значение size_t передается в качестве аргумента шаблона во время компиляции и должно быть const или выражением constexpr . Вы используете его следующим образом:
MyArray arr;
Другие типы значений, включая указатели и ссылки, могут передаваться в виде параметров, отличных от типа. Например, можно передать указатель на объект функции или функции, чтобы настроить некоторую операцию внутри кода шаблона.
Вычет типов для параметров шаблона, не относящихся к типу
В Visual Studio 2017 и более поздних версиях и в /std:c++17 режиме или более поздних версиях компилятор выводит тип аргумента шаблона, отличного от типа, объявленного с помощью auto :
template constexpr auto constant = x; auto v1 = constant; // v1 == 5, decltype(v1) is int auto v2 = constant; // v2 == true, decltype(v2) is bool auto v3 = constant; // v3 == 'a', decltype(v3) is char
Шаблоны в качестве параметров шаблона
Шаблон может быть параметром шаблона. В этом примере MyClass2 имеет два параметра шаблона: параметр typename T и параметр шаблона Arr:
template class Arr> class MyClass2 < T t; //OK Arra; U u; //Error. U not in scope >;
Так как сам параметр Arr не имеет текста, его имена параметров не требуются. На самом деле, это ошибка, чтобы ссылаться на имя типа или имена параметров класса Arr из текста MyClass2 . По этой причине имена параметров типа Arr могут быть опущены, как показано в этом примере:
template class Arr> class MyClass2 < T t; //OK Arra; >;
Аргументы шаблона по умолчанию
Шаблоны классов и функций могут иметь аргументы по умолчанию. Если у шаблона есть аргумент по умолчанию, его можно не указать при использовании. Например, шаблон std::vector имеет аргумент по умолчанию для распределителя:
template > class vector;
В большинстве случаев класс std::allocator по умолчанию является допустимым, поэтому используется вектор, как показано ниже:
vector myInts;
Но при необходимости можно указать пользовательский распределитель следующим образом:
vector ints;
При наличии нескольких аргументов шаблона все аргументы после первого аргумента по умолчанию должны иметь аргументы по умолчанию.
При использовании шаблона, параметры которого по умолчанию используются, используйте пустые угловые скобки:
template class Bar < //. >; . int main() < Bar<>bar; // use all default type arguments >
Специализация шаблонов
В некоторых случаях невозможно или желательно определить точно тот же код для любого типа. Например, может потребоваться определить путь к коду, который необходимо выполнить, только если аргумент типа является указателем или std::wstring или типом, производным от определенного базового класса. В таких случаях можно определить специализацию шаблона для конкретного типа. Когда пользователь создает экземпляр шаблона с этим типом, компилятор использует специализацию для создания класса, а для всех других типов компилятор выбирает более общий шаблон. Специализации, в которых все параметры являются специализированными, являются полными специализациями. Если только некоторые из параметров специализированы, он называется частичной специализацией.
template class MyMap*. */>; // partial specialization for string keys template class MyMap *. */>; . MyMap classes; // uses original template MyMap classes2; // uses the partial specialization
Шаблон может иметь любое количество специализаций, если каждый параметр специализированного типа является уникальным. Только шаблоны классов могут быть частично специализированы. Все полные и частичные специализации шаблона должны быть объявлены в том же пространстве имен, что и исходный шаблон.
Дополнительные сведения см. в разделе «Специализация шаблона».
Что значит typename?
typedef я знаю.
typename — не уверен что понимаю это ключевое слово. Вне template как я понял используется для помощи компилятору в определении типа.
а что происходит дальше std::stack::container_type::iterator ? разве у стека есть итератор?
- Вопрос задан более двух лет назад
- 514 просмотров
Комментировать
Решения вопроса 1

Станислав Макаров @Nipheris Куратор тега C++
1. typename в данном случае нужен компилятору только как подсказка от разработчика, что последующий идентификатор (т.е. std::stack
2. Member-тип container_type эквивалентен типу нижележащего контейнера (т.к. std::stack — это адаптер под интерфейс стека, а не реальный контейнер, реальный контейнер для хранения вы выбираете вторым параметром шаблона, по-умолчанию это std::deque).
3. Вот у std::deque итератор действительно есть.
typename
В определениях шаблонов typename предоставляется указание компилятору о том, что неизвестный идентификатор является типом. В списках параметров шаблона используется для указания параметра типа.
Синтаксис
typename identifier ;
Замечания
typename Ключевое слово необходимо использовать, если имя в определении шаблона — это полное имя, зависящее от аргумента шаблона; необязательно, если полное имя не зависит. Дополнительные сведения см. в разделе «Шаблоны» и «Разрешение имен».
typename может использоваться любым типом в любом месте объявления или определения шаблона. Он не допускается в списке базовых классов, если только не в качестве аргумента шаблона в базовый класс шаблона.
template class C1 : typename T::InnerType // Error - typename not allowed. <>; template class C2 : A // typename OK. <>;
Ключевое слово typename также можно использовать вместо class списков параметров шаблона. Например, следующие операторы семантически эквивалентны:
template. template.
Пример
// typename.cpp template class X < typename T::Y m_y; // treat Y as a type >; int main()
Шаблоны классов в С++
Мы уже ранее рассматривали в С++ такой инструмент, как шаблоны, когда создавали шаблоны функций. Почему стоит пользоваться шаблонами, было написано в статье, с шаблонами функций. Там мы рассмотрели основные положения шаблонов в С++. Давайте их вспомним.
Любой шаблон начинается со слова template , будь то шаблон функции или шаблон класса. После ключевого слова template идут угловые скобки — < >, в которых перечисляется список параметров шаблона. Каждому параметру должно предшествовать зарезервированное слово class или typename . Отсутствие этих ключевых слов будет расцениваться компилятором как синтаксическая ошибка. Некоторые примеры объявления шаблонов:
template
template
template
Ключевое слово typename говорит о том, что в шаблоне будет использоваться встроенный тип данных, такой как: int , double , float , char и т. д. А ключевое слово class сообщает компилятору, что в шаблоне функции в качестве параметра будут использоваться пользовательские типы данных, то есть классы. Но не в коем случае не путайте параметр шаблона и шаблон класса. Если нам надо создать шаблон класса, с одним параметром типа int и char , шаблон класса будет выглядеть так:
template class Name < //тело шаблона класса >;
где T — это параметр шаблона класса, который может принимать любой из встроенных типов данных, то, что нам и нужно.
А если параметр шаблона класса должен пользовательского типа, например типа Array , где Array — это класс, описывающий массив, шаблон класса будет иметь следующий вид:
template class Name < //тело шаблона класса >;
C этим вам лучше разобраться изначально, чтобы потом не возникало никаких ошибок, даже, если шаблон класса написан правильно.
Давайте создадим шаблон класса Стек, где стек — структура данных, в которой хранятся однотипные элементы данных. В стек можно помещать и извлекать данные. Добавляемый элемент в стек, помещается в вершину стека. Удаляются элементы стека, начиная с его вершины. В шаблоне класса Stack необходимо создать основные методы:
- Push — добавить элемент в стек;
- Pop — удалить элемент из стека
- printStack — вывод стека на экран;
Итак реализуем эти три метода, в итоге получим самый простой класс, реализующий работу структуры стек. Не забываем про конструкторы и деструкторы. Смотрим код ниже.
#include «stdafx.h» #include using namespace std; #include template class Stack < private: T *stackPtr; // указатель на стек int size; // размер стека T top; // вершина стека public: Stack(int = 10);// по умолчанию размер стека равен 10 элементам ~Stack(); // деструктор bool push(const T ); // поместить элемент в стек bool pop(); // удалить из стека элемент void printStack(); >; int main() < Stack myStack(5); // заполняем стек cout > temp; myStack.push(temp); > myStack.printStack(); // вывод стека на экран cout // конструктор template Stack::Stack(int s) < size = s >0 ? s: 10; // инициализировать размер стека stackPtr = new T[size]; // выделить память под стек top = -1; // значение -1 говорит о том, что стек пуст > // деструктор template Stack::~Stack() < delete [] stackPtr; // удаляем стек >// элемент функция класса Stack для помещения элемента в стек // возвращаемое значение — true, операция успешно завершена // false, элемент в стек не добавлен template bool Stack::push(const T value) < if (top == size - 1) return false; // стек полон top++; stackPtr[top] = value; // помещаем элемент в стек return true; // успешное выполнение операции >// элемент функция класса Stack для удаления элемента из стек // возвращаемое значение — true, операция успешно завершена // false, стек пуст template bool Stack::pop() < if (top == - 1) return false; // стек пуст stackPtr[top] = 0; // удаляем элемент из стека top--; return true; // успешное выполнение операции >// вывод стека на экран template void Stack::printStack() < for (int ix = size -1; ix >= 0; ix—) cout
#include using namespace std; #include template class Stack < private: T *stackPtr; // указатель на стек int size; // размер стека T top; // вершина стека public: Stack(int = 10);// по умолчанию размер стека равен 10 элементам ~Stack(); // деструктор bool push(const T ); // поместить элемент в стек bool pop(); // удалить из стека элемент void printStack(); >; int main() < Stack myStack(5); // заполняем стек cout > temp; myStack.push(temp); > myStack.printStack(); // вывод стека на экран cout // конструктор template Stack::Stack(int s) < size = s >0 ? s: 10; // инициализировать размер стека stackPtr = new T[size]; // выделить память под стек top = -1; // значение -1 говорит о том, что стек пуст > // деструктор template Stack::~Stack() < delete [] stackPtr; // удаляем стек >// элемент функция класса Stack для помещения элемента в стек // возвращаемое значение — true, операция успешно завершена // false, элемент в стек не добавлен template bool Stack::push(const T value) < if (top == size - 1) return false; // стек полон top++; stackPtr[top] = value; // помещаем элемент в стек return true; // успешное выполнение операции >// элемент функция класса Stack для удаления элемента из стек // возвращаемое значение — true, операция успешно завершена // false, стек пуст template bool Stack::pop() < if (top == - 1) return false; // стек пуст stackPtr[top] = 0; // удаляем элемент из стека top--; return true; // успешное выполнение операции >// вывод стека на экран template void Stack::printStack() < for (int ix = size -1; ix >= 0; ix—) cout
Как видите шаблон класса Stack объявлен и определен в файле с main -функцией. Конечно же такой способ утилизации шаблонов никуда не годится, но для примера сойдет. В строках 7 — 20 объявлен интерфейс шаблона класса. Объявление класса выполняется привычным для нас образом, а перед классом находится объявление шаблона, в строке 7. При объявлении шаблона класса, всегда используйте такой синтаксис.
Строки 47 — 100 содержат элемент-функции шаблона класса Stack, причем перед каждой функцией необходимо объявлять шаблон, точно такой же, как и перед классом — template . То есть получается, элемент-функции шаблона класса, объявляются точно также, как и обычные шаблоны функций. Если бы мы описали реализацию методов внутри класса, то заголовок шаблона — template для каждой функции прописывать не надо.
Чтобы привязать каждую элемент-функцию к шаблону класса, как обычно используем бинарную операцию разрешения области действия — :: с именем шаблона класса — Stack . Что мы и сделали в строках 49, 58, 68, 83, 96.
Обратите внимание на объявление объекта myStack шаблона класса Stack в функции main , строка 24. В угловых скобочка необходимо явно указывать используемый тип данных, в шаблонах функций этого делать не нужно было. Далее в main запускаются некоторые функции, которые демонстрируют работу шаблона класса Stack . Результат работы программы смотрим ниже.
CppStudio.com
Заталкиваем элементы в стек: 12 3456 768 5 4564 |4564 | 5 | 768 |3456 | 12 Удаляем два элемента из стека: | 0 | 0 | 768 |3456 | 12
К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!