И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 31
Текст из файла (страница 31)
Зато средства статической параметризации языка Java обеспечивают полную совместимость с уже разработанными пакетами классов.Это очень полезное свойство, учитывая, что обобщения появилисьв языке только в 2005 г. после 10 лет активного программированияна Java, в процессе которого были созданы тысячи программ и библиотек пакетов.Несмотря на существенные различия, механизмы параметрического полиморфизма во всех языках позволяют обеспечить повышеннуюнадежность создаваемых абстракций за счет статического контролясоответствия типов.Наиболее естественное и популярное применение параметрического полиморфизма — это создание параметризованных классовколлекций, способных хранить объекты одного типа.157Далее рассмотрим основные понятия механизмов статическойпараметризации в языках C++, C# и Java.
Более подробно эти средства для C++ рассматриваются в [1, 9, 28], для C# — в [23], а дляJava — в [35].10.2. Шаблоны языка C++Для описания шаблонов используется ключевое слово template,после которого указываются формальные параметры шаблона, заключенные в угловые скобки. Формальные параметры шаблона (илипросто параметры) перечисляются через запятую. Формальный параметр шаблона может быть именем типа (вид параметра class Т илиtypename т) или именем переменной (вид параметра тип X):template <списокили_классапараметров>объявление_функции_Имя класса (функции) в объявлении шаблона класса (функции)называется именем шаблона (не путайте с идентификатором шаблона, рассматриваемым далее).Хорошим кандидатом на статическую параметризацию являетсякласс Vector, рассмотренный ранее.
Очевидно, что его следуетпараметризовать типом хранимого элемента, тогда получится универсальный массив для хранения данных практически любого типа.Менее очевидным является второй параметр шаблона — размервектора. Если сделать его статическим параметром, то тело векторане надо располагать в динамической памяти, не надо хранить длинувектора, а вектор в этом случае упрощается: он становится «плоской»структурой, для которой подходят неявные побитовые присваиваниеи конструктор копирования. При этом также не требуется деструктор.Все это, правда, за счет некоторой потери гибкости (ведь параметробязан быть константным выражением).
Например:template <typename Т, int size> class Vector{T body[size];publ i c :T& operator[](int index){if (index <0 || index >= size)throw s t d ::exception("Bad vector size");return body[index];}T& getAt(int index)};158{ return body[index];}Получившаяся абстракция не уступает по эффективности и надежности встроенным массивам языка Паскаль: использует плоскуюпамять и проверяет индекс массива на корректность во время выполнения. Если же программист уверен в корректности индекса, тоон может использовать более эффективную функцию get At ().Явная конкретизация шаблона класса (в C++ используется термин«инстанцирование») имеет видимя шаблона <аргументы шаблона>Такая конструкция называется в документации по C++ идентификатором шаблона. Аргументы (или фактические параметры)шаблона — это имена типов для параметров-типов и константныевыражения для параметров-переменных.
Конкретизация шаблонаприводит к порождению нового типа. Его именем является идентификатор шаблона. Идентификатор шаблона полностью эквивалентенимени типа. При порождении нового типа может происходить генерация кода для функций — членов нового типа.Разумеется, компилятор (и компоновщик) должен следить за тем,чтобы новые типы (и генерируемый код) не дублировали друг друга.Это нетривиальная задача (учитывая, что конкретизации шаблонамогут быть разбросаны по разным модулям программы), которуюкаждая реализация решает по-своему.Так, следующие конкретизации шаблона Vector обозначают одини тот же тип во всех модулях программы:Vector<int, 32> v;typedef Vector <int, 32> IntVec32;IntVec32 x;const int n = 16;Vector <int, 2*n> z = v;Переменные v, x, z — относятся к одному типу, их можно присваивать друг другу и т.д.Если тип Vector кажется недостаточно гибким, то можно использовать определение вектора из подразд.
7.2, превратив его в шаблони заменив все вхождения типа элемента на т:template ctypename Т> class DynVector{Т * body;int size;p ubl i c :explicit DynVector(int sz) { ...}// реализация других методов~ DynVector () { delete[] body;}};159Полезно дополнить класс дополнительными методами, например:void swap(DynVectorS);// обменивает тела без копирования всего массива// значенийОчевидно, что механизм шаблонов классов — это мощное средстворазвития, позволяющее дополнить язык новыми типами данных.Практически любой содержательный класс, приведенный в данномучебнике (например, complex) можно параметризовать. Эту идеювоплотили в жизнь создатели стандартной библиотеки C++, недаромбольшая часть этой библиотеки называется STL (Standard TemplateLibrary — библиотека стандартных шаблонов).Шаблоны функций объявляются аналогично шаблонам классов.Приведем пример функции вычисления квадрата числового значения:template Ctypename Т> Т square(Т х){return х*х;}Этот шаблон функции может породить семейство конкретныхфункций.
Порождение может происходить в результате явной конкретизации (с указанием идентификатора шаблона):int а = square<int>(127);В момент конкретизации происходит порождение новой функции(с генерацией объектного кода).Заметим, что порожденные функции можно рассматривать каксемейство перегруженных функций (считая, что имя порожденнойфункции совпадает с именем шаблона функции). Тогда возникаетинтересная возможность вызова порожденной функции без явногоуказания параметров шаблона:double d = s q u a r e (127.О ); // square<double>int a,x = 1;a = square(x); // square <int>Поскольку компилятор знает профиль вызова (например, ( i n t ) ),то он может сопоставить его с профилем шаблонной функции ( ( Т))и отождествить (int<->T).
Это процесс называется выводом типа.Идея вывода типов проста, но конкретные правила, как и правила поиска правильной перегрузки, довольно сложны, например,см. [9].Заметим, что процесс вывода приводит к (неявной) конкретизации шаблона функции.
Возможность неявной конкретизации очень160полезна, например, при использовании шаблона операции. Пустьмы хотим создать шаблон функции скалярного произведения двухвекторов и перегрузить ее как операцию умножения:template <typename Т, int size> Т operator *(VectorCT,size>&vl, Vector<T,size>&v2){T sum = 0;for (int i=0; i < size; i++){s u m + = v l .g e t A t (i )* v 2 .getAt (i );return sum;}Очень важно понимать, что для конкретизации необходимоиметь полный текст шаблона (как функции, так и класса, включаяопределения всех его членов). Порождение новой сущности происходит во время конкретизации с учетом контекста конкретизации и полного текста шаблона. Например, определение шаблонаоперации скалярного произведения требует от типа-параметра тналичия и доступности четырех операций: наличия преобразованияиз нуля, операций «+=» и «*», а также конструктора копирования.В контексте определения шаблона непонятно, есть ли эти операции, как их вызывать и т.д.
Поэтому все решения по связываниюкомпилятор может принять, только имея полную информацию како шаблоне, так и о контексте конкретизации. Именно поэтому библиотеки шаблонов распространяются в исходных текстах (иначеих использовать нельзя). Это абсолютно приемлемо для сторонников открытых исходных текстов, но неприемлемо для поборниковавторских прав.В частности, поэтому в языках Java и C# (корпоративные разработки) применяется более слабая модель связывания при статической параметризации, позволяющая использовать обобщенияв оттранслированном виде.Такие же соображения применимы даже к простому шаблонувектора. Тип элементов этого шаблона требует наличия доступногоконструктора умолчания (без этого нельзя завести массив объектовтипа).
Если его в аргументе шаблона нет, то при конкретизациикомпилятор выдаст ошибку.Доступность полной информации как о шаблоне, так и о контексте конкретизации позволяет реализовать такие понятия, какперегрузка шаблонов функций, явная специализация шаблонови частичная специализация шаблонов классов. В результате хорошоспроектированная библиотека шаблонов позволяет генерировать машинный код, сравнимый по эффективности, а то и превосходящийкод, выдаваемый оптимизирующими компиляторами императивныхязыков типа С и Фортрана.161Перегрузка шаблонов функцийРассмотрим новый вариант шаблона функции возведения в квадрат, который использует операцию скалярного произведения:template Ctypename Т, int N> Т square (Vector<T,N>& v){return v*v;}Приведем пример использования разных вариантов square:Vector <double, 64> v;double d = square(1.5); // square <double>d = square(v); // square <Vector<double,64>>Явная специализация шаблоновД л я я в н о й специализации необходим общий шаблон.
Обычнотакой шаблон реализует универсальный алгоритм или структуру.Однако может возникнуть ситуация, в которой для конкретноговарианта аргументов шаблона он либо неэффективен, либо вообщенеправильно работает. В этом случае можно использовать явнуюспециализацию.Явная специализация шаблона порождает вариант шаблонадля конкретного набора его аргументов. Явную специализациюиногда путают с конкретизацией, ведь и при конкретизации тожеуказываются аргументы. Однако это совершенно разные понятия.Конкретизация указывает компилятору следующее: возьми шаблон,примени к нему аргументы и породи новый класс (функцию), а явная специализация — это указание взять новое описание старогошаблона, набор аргументов и считать, что это новый шаблон, который применим только к этому набору и порождает только одинвариант.Явная специализация шаблона функции — это конкретная функция с именем шаблонной функции, перед объявлением которой стоитtemplateо:template ообъявление_обычной_функцииВпрочем, template о можно опускать, тогда явная специализация будет иметь вид объявления функции (это сделано для совместимости со старыми компиляторами).Когда компилятор видит вызов функции с именем шаблона, тоон пытается сначала найти явную специализацию, и только если еенет, ищет шаблон.Примером является функция вычисления максимума:162template <typename T> T max(T a, T b){return a > b ? a : b;}Однако для строк языка С (и строковых литералов), т.
е. типаconst char *, шаблон работает неверно: он использует операциюсравнения указателей, которая бессмысленна в данном контекстедля строк. В этом случае следует написать конкретный вариант длястрок языка С, использующий операцию сравнения из его стандартной библиотеки (которая, кстати, является и частью стандартнойбиблиотеки C++):template оchar * b)const char * max(const char * a, const{return strcmp(a,b) > 0 ? a: b;}int q = max (1,2); // шаблонconst char * p = max ("line2", "linel");// специализацияАналогичный вид имеет и явная специализация шаблона класса,только в ней уже нельзя опускать template о и требуется явноуказать аргументы шаблона:template <typename Т> class Container // общий шаблон{// реализация контейнера для любых типов};template о class Container<const char *>// специализация{// оптимизированная реализация для строк};Интересно, что интерфейс общего шаблона может отличаться отинтерфейса специализированного шаблона (другой профиль методови даже другой набор методов).Частичная специализация шаблонов классовЧастичная специализация также хороша тогда, когда общий шаблон не подходит по каким-либо причинам.